JavaScript: How to define methods dynamically? - javascript

Object.defineProperty can be used to define properties and getter / setters. But it doesn't seem to support setting properties that are functions (method). Why?
var obj = {};
Object.defineProperty(obj, 'myMethod', function () {
console.log('Hello!');
})
After this, obj.myMethod is undefined.

The third parameter you pass to Object.defineProperty should be an object with a value property, if you want to do something like this:
var obj = {};
Object.defineProperty(obj, 'myMethod', { value: function () {
console.log('Hello!');
}})
obj.myMethod();
This would throw if you tried to pass a non-function:
var obj = {};
Object.defineProperty(obj, 'myMethod', 'foo')
But functions are objects too, so in your original code, the defineProperty call silently fails.

You can use this pattern:
var obj = { c: 3 };
obj = Object.assign({}, obj, {
a: function(){
console.log("testing");
}
});
obj.a();
console.log(obj.c);

From the official documentation of MDN. I recommend you to read the issues related with creating dynamic functions before actually implementing one.
This aside here is an example:
function testMe () {
var obj = {}
obj.myFunc = new Function('a', 'b', 'return a + b');
var el = document.getElementById("test").innerHTML = obj.myFunc(1, 2);
}
<div id="test"></div>
<button onclick="testMe();">Try</button>

Before understanding some points
Syntax : Object.defineProperty(obj, prop, descriptor)
obj The object on which to define the property.
prop The name or Symbol of the property to be defined or modified.
descriptor The descriptor for the property being defined or modified.
const obj = {};
Object.defineProperty(obj, 'myobject', {
value: 20,
writable: false
});
obj.myobject = 10;
document.write(obj.myobject);

Related

JavaScript use original getter/setter in defineProperty

I would like to create a TypeScript decorator that can extend the logic of a property's getter/setter. I have tried to copy the original property under a symbol and call that when I redefine the property. The problem is it turns into an infinite loop.
//Find the latest version of 'attribute' getter setter in the prototype chain
let obj = _object;
while(obj && !(Object.getOwnPropertyDescriptor(obj, 'attribute'))){
obj = Object.getPrototypeOf(obj);
}
//Copy original 'attribute' logic under a symbol
const attributeDesc = Object.getOwnPropertyDescriptor(obj, 'attribute');
let id=Symbol('__attribute');
Object.defineProperty(obj, id, attributeDesc);
//Redefine 'attribute' logic
Object.defineProperty(_object, 'attribute', {
get: () => {
//call original
const attribute = obj[id]; //It crashes the page (probably infinite loop)
//extend original logic
attribute['extend'] = 'property';
return attribute;
},
enumerable: false,
configurable: true
});
If you could explain me why it ends up this way that would help me out. I thought the new getter function reference nothing to do with the original. Please suggest me a solution to achive this in JavaScript.
Thank you for your time and answers!
I don't quite see the error. In the repro you provided, it's logical that there is one: the getter for attribute property is calling itself on the line var attributes = obj[id], so there is an infinite loop. However if you edit your code to be like the snippet you provided in the question:
class A {
get attribute() {
return { a: 1 }
}
}
var _object = new A()
let obj = _object
while (obj && !Object.getOwnPropertyDescriptor(obj, 'attribute')) {
obj = Object.getPrototypeOf(obj)
}
const attributeDesc = Object.getOwnPropertyDescriptor(obj, 'attribute')
let id = Symbol('__attribute')
Object.defineProperty(obj, id, attributeDesc)
Object.defineProperty(obj, 'attribute', {
get: function () {
var attributes = obj[id]
attributes['extend'] = 'property'
return attributes
},
enumerable: false,
configurable: true,
})
console.log('result:', obj.attribute)
There is no error and it works as expected.
You don't really need the symbol though, you could do something like
function extendAttributes(_object) {
let obj = _object
while (obj && !Object.hasOwnProperty(obj, 'attributes')) {
obj = Object.getPrototypeOf(obj)
}
if(!obj) return;
const oldContainer = {}
const attributesDescriptor = Object.getOwnPropertyDescriptor(obj, 'attributes')
Object.defineProperty(oldContainer, 'attributes', attributesDescriptor)
Object.defineProperty(obj, 'attributes', {
get() {
const attribute = oldContainer.attributes;
//extend original logic
attribute['extend'] = 'property';
return attribute;
}
})
}
class A {
get attributes() { return {a: 1} }
}
const obj = new A()
extendAttributes(obj)
console.log(obj.attributes)
Which also works like expected

Creating object dynamically using Object.create()

I have a scenario, where I need to create objects dynamically.
In my example, an object meta contains the name for the constructor function to be used during initialization Object.create().
At the moment using the following code, I am able to create the objects dynamically but the property name is not defined.
I need that property on the result;
What is wrong in my script? Do you know a better way to achieve the same result?
(function () {
var costructors = {
A: function () {
this.name = 'A';
console.log(this.name);
},
B: function () {
this.name = 'B';
console.log(this.name);
},
C: function () {
this.name = 'C';
console.log(this.name);
}
},
meta = {
A: true,
B: true,
C: true,
},
result = [];
function createObjs() {
Object.keys(meta).forEach(function (type) {
var obj = Object.create(costructors[type].prototype);
result.push(obj);
}.bind(this));
}
createObjs.call(this);
console.log(result);
})();
You haven't defined a prototype for any of the constructors, so you're not creating the name in your instances, since you're creating an object from their prototype, not from their constructor. Try
Object.create(constructors[type])
An alternative without using Object.create would be:
var obj = new costructors[type]();
instead of:
var obj = Object.create(costructors[type].prototype);
Actually, Object.create does not call the constructor function, but only creates a new object from the given prototype. Any member variables can be provided via a property object:
var obj = Object.create(
constructors[type].prototype,
{ 'name' : { value: 'A', writable: true}}
);

Javascript overriding the property setting functionality

JavaScript is dynamic. Cool !
I have the following constructor function :
function Preferences () {
this._preferences = {}
}
var obj = new Preferences()
I want to achieve something like this:
>>> obj.something = 'value'
>>> this._preferences['something']
'value'
That is setting the property of the obj does not actually set it's own property but that of obj._preferences. That is I want to override the default behavior.
Is it possible ?
EDIT : I want to achieve this for all property names i.e the name of the property to be set is not already known.
Object.defineProperty(Preferences.prototype, 'something', {
get: function(){
return this._preferences.something;
},
set: function(value){
this._preferences.something = value;
}
});
should do it. It defines a property, 'something', using an accessor property instead of a data property, and will call the 'get' and 'set' functions to decide what do so when .something is accessed.
SOLUTION 1
Using Proxy object you can do something like this and handle runtime defined properties
function Preferences() {
this._preferences = {};
var prefProxy = new Proxy(this, {
get: function(target, property) {
return property in target?
target[property]:
target._preferences[property];
}
set: function(target, property, value, receiver) {
if(property in target){
target[property] = value;
} else {
target._preferences[property] = value;
}
}
});
return prefProxy;
};
SOLUTION 2
I can be wrong but i think what you are asking is solved returning _preferences
function Preferences () {
this._preferences = {};
return _preferences;
}
var obj = new Preferences()
SOLUTION 3
Using getter and setter you can redirect the property to _preferences
function Preferences () {
this._preferences = {}
Object.defineProperty(Preferences.prototype, 'something', {
get: function() {
return this._preferences['something'];
},
set: function(value) {
this._preferences['something'] = value;
}
});
}
var obj = new Preferences()

How to clone a prototype with property methods?

I am using the Typed.React library which includes a method to extend one prototype definition with that of another:
function extractPrototype(clazz) {
var proto = {};
for (var key in clazz.prototype) {
proto[key] = clazz.prototype[key];
}
return proto;
}
If the provided class defines property methods, this function has a side effect of executing the get method e.g.
var TestObject = (function () {
function TestObject() {
this.str = "test string";
}
Object.defineProperty(TestObject.prototype, "TestProperty", {
get: function () {
console.log("exec get");
return this.str;
},
set: function (value) {
console.log("exec set");
this.str = value;
},
enumerable: true,
configurable: true
});
return TestObject;
})();
var extracted = extractPrototype(TestObject);
When extactPrototype accesses TestObject.prototype["TestProperty"], it will execute the property get method and print:
exec get
How would I duplicate a prototype with property methods without executing them?
I think you are looking for the new ES6 Object.assign function.
Of course there's a simpler fix to your problem - just don't access and set properties, copy their property descriptors:
function extractPrototype(clazz) {
var proto = {};
for (var key in clazz.prototype) {
Object.defineProperty(proto, key, Object.getOwnPropertyDescriptor(clazz.prototype, key));
}
return proto;
}

JavaScript: How to pass object by value?

When passing objects as parameters, JavaScript passes them by reference and makes it hard to create local copies of the objects.
var o = {};
(function(x){
var obj = x;
obj.foo = 'foo';
obj.bar = 'bar';
})(o)
o will have .foo and .bar.
It's possible to get around this by cloning; simple example:
var o = {};
function Clone(x) {
for(p in x)
this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
}
(function(x){
var obj = new Clone(x);
obj.foo = 'foo';
obj.bar = 'bar';
})(o)
o will not have .foo or .bar.
Question
Is there a better way to pass objects by value, other than creating a local copy/clone?
Not really.
Depending on what you actually need, one possibility may be to set o as the prototype of a new object.
var o = {};
(function(x){
var obj = Object.create( x );
obj.foo = 'foo';
obj.bar = 'bar';
})(o);
alert( o.foo ); // undefined
So any properties you add to obj will be not be added to o. Any properties added to obj with the same property name as a property in o will shadow the o property.
Of course, any properties added to o will be available from obj if they're not shadowed, and all objects that have o in the prototype chain will see the same updates to o.
Also, if obj has a property that references another object, like an Array, you'll need to be sure to shadow that object before adding members to the object, otherwise, those members will be added to obj, and will be shared among all objects that have obj in the prototype chain.
var o = {
baz: []
};
(function(x){
var obj = Object.create( x );
obj.baz.push( 'new value' );
})(o);
alert( o.baz[0] ); // 'new_value'
Here you can see that because you didn't shadow the Array at baz on o with a baz property on obj, the o.baz Array gets modified.
So instead, you'd need to shadow it first:
var o = {
baz: []
};
(function(x){
var obj = Object.create( x );
obj.baz = [];
obj.baz.push( 'new value' );
})(o);
alert( o.baz[0] ); // undefined
Check out this answer https://stackoverflow.com/a/5344074/746491 .
In short, JSON.parse(JSON.stringify(obj)) is a fast way to copy your objects, if your objects can be serialized to json.
Here is clone function that will perform deep copy of the object:
function clone(obj){
if(obj == null || typeof(obj) != 'object')
return obj;
var temp = new obj.constructor();
for(var key in obj)
temp[key] = clone(obj[key]);
return temp;
}
Now you can you use like this:
(function(x){
var obj = clone(x);
obj.foo = 'foo';
obj.bar = 'bar';
})(o)
Use Object.assign()
Example:
var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.
A cleaner example that does the same thing:
var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Use this
x = Object.create(x1);
x and x1 will be two different object,change in x will not change x1
You're a little confused about how objects work in JavaScript. The object's reference is the value of the variable. There is no unserialized value. When you create an object, its structure is stored in memory and the variable it was assigned to holds a reference to that structure.
Even if what you're asking was provided in some sort of easy, native language construct it would still technically be cloning.
JavaScript is really just pass-by-value... it's just that the value passed might be a reference to something.
ES6
Using the spread operator like obj2 = { ...obj1 }
Will have same values but different references
ES5
Use Object.assign obj2 = Object.assign({}, obj1)
Javascript always passes by value. In this case it's passing a copy of the reference o into the anonymous function. The code is using a copy of the reference but it's mutating the single object. There is no way to make javascript pass by anything other than value.
In this case what you want is to pass a copy of the underlying object. Cloning the object is the only recourse. Your clone method needs a bit of an update though
function ShallowCopy(o) {
var copy = Object.create(o);
for (prop in o) {
if (o.hasOwnProperty(prop)) {
copy[prop] = o[prop];
}
}
return copy;
}
As a consideration to jQuery users, there is also a way to do this in a simple way using the framework. Just another way jQuery makes our lives a little easier.
var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy = jQuery.extend(true, {}, o);
references :
http://api.jquery.com/jquery.extend/
https://stackoverflow.com/a/122704/1257652
and to dig into the source.. http://james.padolsey.com/jquery/#v=1.8.3&fn=jQuery.extend
Actually, Javascript is always pass by value. But because object references are values, objects will behave like they are passed by reference.
So in order to walk around this, stringify the object and parse it back, both using JSON. See example of code below:
var person = { Name: 'John', Age: '21', Gender: 'Male' };
var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object
var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects
I needed to copy an object by value (not reference) and I found this page helpful:
What is the most efficient way to deep clone an object in JavaScript?. In particular, cloning an object with the following code by John Resig:
//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
With the ES6 syntax:
let obj = Object.assign({}, o);
When you boil down to it, it's just a fancy overly-complicated proxy, but maybe Catch-All Proxies could do it?
var o = {
a: 'a',
b: 'b',
func: function() { return 'func'; }
};
var proxy = Proxy.create(handlerMaker(o), o);
(function(x){
var obj = x;
console.log(x.a);
console.log(x.b);
obj.foo = 'foo';
obj.bar = 'bar';
})(proxy);
console.log(o.foo);
function handlerMaker(obj) {
return {
getOwnPropertyDescriptor: function(name) {
var desc = Object.getOwnPropertyDescriptor(obj, name);
// a trapping proxy's properties must always be configurable
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
getPropertyDescriptor: function(name) {
var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
// a trapping proxy's properties must always be configurable
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
getOwnPropertyNames: function() {
return Object.getOwnPropertyNames(obj);
},
getPropertyNames: function() {
return Object.getPropertyNames(obj); // not in ES5
},
defineProperty: function(name, desc) {
},
delete: function(name) { return delete obj[name]; },
fix: function() {}
};
}
If you are using lodash or npm, use lodash's merge function to deep copy all of the object's properties to a new empty object like so:
var objectCopy = lodash.merge({}, originalObject);
https://lodash.com/docs#merge
https://www.npmjs.com/package/lodash.merge

Categories

Resources