Is it possible to automatically stringify in JavaScript? - javascript

I know I can create a toString() function on an object, so that every time it's printed or treated like a string it will first stringify the object with that function.
Is it possible to do that directly so I can use String object functions on the object?
var SomeObject = function(a, b){
this.a = a;
this.b = b
}
SomeObject.prototype.toString = function(){
return [ this.a, this.b ].join(' ')
}
var objInstance = new SomeObject('this', 'that');
console.log(objInstance + '') // This that
console.log(("" + objInstance).split('')) // [ 't', 'h', 'i', 's', ' ', 't', 'h', 'a', 't' ]
console.log(objInstance.split()) // Error
Is it possible to do so that the object "behaves" like a string when a String function is called on it?
In other words, I'd like objInstance.split() to have the same result as ("" + objInstance).split(''), and also objInstance.length or objInstance.match(/something/), etc.

You can let your objects inherit from String so that all string methods become available:
class SomeObject extends String {
constructor(a, b) {
super(a + " " + b);
this.a = a;
this.b = b;
}
}
var obj = new SomeObject('this', 'that');
console.log(obj.split(""));
No need to use complicated Proxy solutions :-)
All the String.prototype methods (except for .toString, .valueOf and [Symbol.iterator]) are "intentionally generic; [they do] not require that its this value be a String object. Therefore, [they] can be transferred to other kinds of objects for use as a method." You can call them on any value, they will coerce it to a string (using .toString() or .valueOf as usual).
You don't even need to use ES6 class extends to inherit from the builtin (which also makes your string value immutable), it works in ES5 as well:
function SomeObject(a, b) {
this.a = a;
this.b = b;
}
SomeObject.prototype = Object.create(String.prototype);
SomeObject.prototype.constructor = SomeObject;
SomeObject.prototype.toString = function() {
return this.a + " " + this.b;
};
var obj = new SomeObject('this', 'that');
console.log(obj.split(""));

One option would be to return a Proxy that checks whether the property exists on String.prototype, and if it does, calls that property with the string that represents the object:
// Declare the proxy handler up front here
// to avoid unnecessary creation of duplicate handler objects
const handler = {
get(obj, prop) {
if (obj[prop] !== undefined) {
return obj[prop];
}
const stringMethod = String.prototype[prop];
if (stringMethod) {
return stringMethod.bind(obj.a + ' ' + obj.b);
}
},
};
const SomeClass = function(a, b) {
this.a = a;
this.b = b
return new Proxy(this, handler);
}
const instance = new SomeClass('this', 'that');
// String methods:
console.log(instance.trim());
console.log(instance.includes('this'));
console.log(instance.includes('somethingelse'));
console.log(instance.split(''));
// Can still assign and retrieve values directly on the object as normal:
instance.foo = 'foo';
console.log(instance.foo);

One option to extend the SomeObject too, something like this.
var SomeObject = function(a, b){
this.a = a;
this.b = b
}
SomeObject.prototype.toString = function(){
return [ this.a, this.b ].join(' ')
};
SomeObject.prototype.split = function() {
return String.prototype.split.apply(this.toString(), arguments);
};
var objInstance = new SomeObject('this', 'that');
console.log(objInstance + '') // this that
//console.log(("" + objInstance).split('')) // [ 't', 'h', 'i', 's', ' ', 't', 'h', 'a', 't' ]
console.log(objInstance.split(''));
In a comment you've asked:
I was thinking about doing this programmatically by doing it for all functions - but is there a way to list all functions of an object?
Yes, you'd use getOwnPropertyNames on String.prototype and filter out the ones that aren't functions:
var SomeObject = function(a, b){
this.a = a;
this.b = b
}
SomeObject.prototype.toString = function(){
return [ this.a, this.b ].join(' ')
};
Object.getOwnPropertyNames(String.prototype).forEach(function(name) {
var fn = String.prototype[name];
if (name !== "toString" && typeof fn === "function") {
SomeObject.prototype[name] = function() {
return fn.apply(this.toString(), arguments);
};
}
});
var objInstance = new SomeObject('this', 'that');
console.log(objInstance + '') // this that
//console.log(("" + objInstance).split('')) // [ 't', 'h', 'i', 's', ' ', 't', 'h', 'a', 't' ]
console.log(objInstance.split(''));

Related

Wants to call methods from string but using prototype doesn't work

Wants to make class method that receives string and returns function. First I made it using switch method. It is an example and there would be more methods in myMethod and also another myMethod like myMethod2.
// using switch
class MyClass {
constructor(a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.e = d;
}
myMethod(methodName) {
switch (methodName) {
case "addA":
return (num) => {
this.a += num;
};
break;
case "addB":
return (num) => {
this.b += num;
};
break;
case "mulA":
return (num) => {
this.a *= num;
};
break;
case "mulB":
return (num) => {
this.b *= num;
};
break;
default:
return () => {};
}
}
}
const obj1 = new MyClass(1, 2, 3, 4);
obj1.myMethod("addA")(3);
obj1.myMethod("mulB")(4);
console.log(obj1); // {a:4, b:8, c:3, e:4}
So I can call methods from string and functions can be divided into types.
then I tried this switch style to lookup function table.
// using hashMap
class MyClass {
constructor(a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.e = d;
}
myMethod(methodName) {
const methods = {
addA: (num) => {
this.a += num;
},
addB: (num) => {
this.b += num;
},
mulA: (num) => {
this.a *= num;
},
mulB: (num) => {
this.b *= num;
},
// and many more methods
};
return methods[methodName];
}
}
const obj1 = new MyClass(1, 2, 3, 4);
obj1.myMethod("addA")(3);
obj1.myMethod("mulB")(4);
console.log(obj1); // {a:4, b:8, c:3, e:4}
My first question is when calling this method, do myMethod makes methods object(methods ={ // string => function hash map } ) every time?
I thought it is true. So I tried to add methods object to prototype.
// prototype
function MyClass(a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
MyClass.prototype.myMethod = {
addA: (num) => {
this.a += num;
},
addB: (num) => {
this.b += num;
},
mulA: (num) => {
this.a *= num;
},
mulB: (num) => {
this.b *= num;
},
// and many more methods
};
const obj1 = new MyClass(1, 2, 3, 4);
obj1.myMethod["addA"](3);
obj1.myMethod["mulB"](4);
console.log(obj1); // {a:1, b:2, c:3, d:4} failed
Then it fails. If an answer of my first question is false, I'll do that way. Or is there a way to work in a prototype way? I'm trying to learn how to write prototype but I'm not sure why this doesn't work.
That's because your this context looks on myMethod object. You don't have any variables in this object that you declared in MyClass
Try to declare myMethod via anonymous function (read comment in code)
MyClass.prototype.myMethod = function() {
// here you have context `this` of MyClass
// you can save `this` in variable and use it in object below
return {
addA: function(num) {
console.log(this);
this.a += num;
},
addB: (num) => {
this.b += num;
},
mulA: (num) => {
this.a *= num;
},
mulB: (num) => {
this.b *= num;
},
// and many more methods
}
};

How to create an object with a property that is the sum of other properties in the same object in Javascript

I'm having a lot of trouble with this and cannot seem to find the correct answer anywhere. I'm creating an object A with various properties, and one of those properties is the sum of two others, like below:
var A = {
a:0,
b:0,
c: this.a + this.b
};
Where my issue comes in is that at run time when I reference A.c, I get NaN instead of 0, or whatever value it should be based on what I have assigned to a and b.
Please he;p. I'm completely confused.
Because the context this is not a reference of object A, rather is a reference
of the enclosing scope, i.e window.
So, this.a + this.b --> undefined + undefined --> NaN.
Example to illustrate the enclosing scope (in this case the object window):
var a = 5;
var b = 5;
var A = {
a: 0,
b: 2,
c: this.a + this.b
};
console.log(A.c);
An alternative is binding a function to that property c using a getter.
var A = {
a: 0,
b: 2,
get c() {
return this.a + this.b
}
};
console.log(A.c);
A.a = 33;
console.log(A.c);
Wrap A's c value (A.c) into a function like this:
var A = {
a:9,
b:2,
c:function() {
return this.a + this.b
}
};
console.log(A.c());
var obj = {
evtChange: function (v) { /* event on any (a, b) changes */
console.log("property changed >> " + v)
},
_a: 70,
_b: 80,
set a(v) {
if (v !== this._a) { /* to prevent unwanted recalculation for complexe calcul statements */
this._a = v;
this.c = this._a + this._b;
this.evtChange(this.c);
}
},
get a() {
return this._a;
},
set b(v) {
if (v !== this._b) { /* to prevent unwanted recalculation for complexe calcul statements */
this._b = v;
this.c = this._a + this._b
this.evtChange(this.c);
}
},
get b() {
return this._b;
},
/* using getter memo
** if we call obj.c first, getter methode will be replaced with a property.
** getter methode will be replaced too on any setters (a, b) call
*/
get c() {
delete this.c; // delete getter
return this.c = this._a + this._b // replace getter imediately with c property
}
};
console.log(obj);
console.log(obj.c);
console.log(obj);
obj.a = 500;
obj.b = 2000;
console.log(obj.c);
console output:
/*obj*/
{evtChange: ƒ, _a: 70, _b: 80}
/*obj.c*/
150
/*obj*/
{evtChange: ƒ, _a: 70, _b: 80, c: 150}
/*obj.a = 500*/
property changed >> 580
/*obj.b = 2000;*/
property changed >> 2500
/*obj.c*/
2500

What is the best way in JavaScript to trim down the properties of an object?

Situation:
I have an object like
{ prop_1 : val_1, prop_2 : val_2, prop_3 : val_3 , ..., prop_N : val_N }
and I want to remove all properties that aren't prop_i, prop_j or prop_K ?
What is the best way to do this other than the "brute force" way of
var original = { prop_1 : val_1, prop_2 : val_2, prop_3 : val_3 , ..., prop_N : val_N };
var newguy = { prop_i : original.prop_i, prop_j : original.prop_j, prop_k : original.prop_k };
original = newguy;
????
Well you can do a function to help you do that.
(function() {
'use strict';
function copyOnly(obj, keysToPreserve) {
var result = {};
for (var i = 0, length = keysToPreserve.length; i < length; ++i) {
var key = keysToPreserve[i];
result[key] = obj[key];
}
return result;
}
function copyExclude(obj, keysToExclude) {
var result = {};
for (var key in obj) {
if (obj.hasOwnProperty(key) && keysToExclude.indexOf(key) === -1) { // -1 means key doesn't exist in keysToExclude
result[key] = obj[key];
}
}
return result;
}
var original = {
a: '1',
b: '2',
c: '3',
d: '4',
e: '5'
};
var toPreserve = ['a', 'b', 'c'];
var result1 = copyOnly(original, toPreserve);
var toExclude = ['d', 'e'];
var result2 = copyExclude(original, toExclude);
// result1 will have the same structure as result2
document.getElementById('result').innerHTML = 'result1 = ' + JSON.stringify(result1) + '\n' + 'result2 = ' + JSON.stringify(result2);
})();
<pre id="result"></pre>
Here is a non-brute-force way. It uses a whitelist, iterates over them, and copies values from "oldguy".
var oldguy = {
"prop_1": 1,
"prop_2": 2,
"prop_3": 3,
"prop_i": "i",
"prop_j": "j",
"prop_k": "k",
"prop_N": "N",
"prop_z": "Z"
};
var newguy = {};
var keys_to_include = ['prop_i', 'prop_j', 'prop_k'];
keys_to_include.forEach(function(k){
newguy[k] = oldguy[k];
});
$('#output').html( JSON.stringify(newguy,null,' ') );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre><code id="output"></code></pre>
Simple .forEach and .indexOf over Object.keys to delete non-matches
function cleanExcept(o, whitelist) {
Object.keys(o).forEach(k => whitelist.indexOf(k) !== -1 ? 0 : delete o[k]);
return o;
}
var o = {foo: 'foo', bar: 'bar', fizz: 'fizz', buzz: 'buzz'};
cleanExcept(o, ['foo', 'fizz']); // Object {foo: "foo", fizz: "fizz"}
Using an Object cache instead of .indexOf, as per #dandavis
function cleanExcept(o, whitelist) {
var w = {};
whitelist.forEach(k => w[k] = true);
Object.keys(o).forEach(k => w[k] ? 0 : delete o[k]);
return o;
}
Modifying this cache just take the values you want and return it (i.e. you get a new object reference)
function cleanExcept(o, whitelist) {
var w = {};
whitelist.forEach(k => !(k in o) ? 0 : w[k] = o[k]);
return w;
}
In ES6, you can write
({prop_i, prop_j, prop_K}) => ({prop_i, prop_j, prop_K})(original)
This works by defining a function which deconstructs its arguments into certain property values, and returns an object with those values. Then call the function on the input object.
See One-liner to take some properties from object in ES 6.

Use apply or call with "old" context

B = function(fn) {
var ex_array = ['a', 'b'];
this.b_fn = function () {
fn.apply(what_should_be_there, ex_array);
}
}
A = function() {
this.a_fn = function (a, b) {
//do sth
}
var b = new B(fn);
}
Only what I would like to do is use apply with on function fn in object b, but with "old" context, I mean in this case with context of object of class A
Here's a solution where a_fn isn't prebound to an arguments array. I added some logging as demo:
var B = function(fn) {
var ex_array = [ 'a', 'b' ];
this.b_fn = function () {
fn(ex_array);
};
};
var A = function() {
this.c = 'hello world';
this.a_fn = function(a, b) {
console.log('a:', a);
console.log('b:', b);
console.log('c:', this.c);
};
var b = new B(Function.prototype.apply.bind(this.a_fn, this));
b.b_fn();
};
Edit
If you want to use ex_array as an array of arguments so that apply will iterate through them, do this (jsfiddle):
B = function(fn) {
var ex_array = ['a', 'b'];
this.b_fn = function() {
return fn.apply(null, ex_array);
};
}
A = function() {
this.a_fn = function (a, b) {
console.log(this, a, b);
}
var b = new B(this.a_fn.bind(this));
b.b_fn(); // => A {}, "a", "b"
}
new A();
Previous answer
See this working demo on jsfiddle:
B = function(fn) {
var ex_array = ['a', 'b'];
this.b_fn = fn.bind(null, ex_array);
}
A = function() {
this.a_fn = function (a, b) {
console.log(this, a);
}
var b = new B(this.a_fn.bind(this));
b.b_fn(); // => A {}, ['a', 'b'];
}
new A();
Basically, all you need to do is bind this.a_fn with its current context. The trick is to use .bind(null, /* args */) when making it this.b_fn, so it keeps the original A context, but allows you to pass in another parameter (in this case, ['a', 'b'].

Initialize a JavaScript object using JSON

I want to do the following
var my_json = {
a : 'lemon',
b : 1
}
function obj(json){
this.a = 'apple';
this.b = 0;
this.c = 'other default';
}
after assigning
var instance = obj(my_json)
I want to get
instance.a == 'lemon'
for(var key in json) {
if(json.hasOwnProperty(key)) {
this[key] = json[key];
}
}
The if block is optional if you know for sure that nothing is every going to extend Object.prototype (which is a bad thing anyway).
If you want defaults how about;
function obj(json){
var defaults = {
a: 'apple',
b: 0,
c: 'other default'
}
for (var k in json)
if (json.hasOwnProperty(k))
defaults[k] = json[k];
return defaults
}

Categories