I have a javascript "class", that I instanciate several times.
My issue is that modifying properties from the instances does actually modify those properties of the "class", so further instances are not initialized in the way I'd like.
Here is a simplified snippet of what happens, the original code is using BackboneJS but this is enough to illustrate my case:
var foo = function() {
this.defaults.id = 1;
};
// This is my property
foo.prototype.defaults = {
id: 0,
};
console.log( foo.prototype.defaults ); // => {id: 0}
var bar = new foo();
console.log( foo.prototype.defaults ); // => {id: 1}
I thought that the "new" keyword would make a brand new object but it seems that it just makes a reference to the prototype properties.
The best option I came with is to clone all properties from inside the constructor...
Do anyone have a clean way to achieve a clean "Class Property" behavior ?
Many thanks in advance,
Romain.
Edit: It does not work from the SO snippet... just copy/paste it to your console if you want.
The prototype is shared among all objects of this type. A new copy is not made. The usual way to initialize properties that are to be unique to this instance is to initialize them directly in the constructor:
this.property = "foo";
This will override any property of the same name in the prototype and will be unique per instance.
In general, the prototype is not used for data properties because they are shared among all instances. If you want to initialize an object with a default set of data properties, the "best practice" for that is to assign a default set of properties into the object instance and then set any overrides. You can't just assign a default object because objects are assigned by pointer, not by copy so all objects would still be pointing at the same object (the same issue you have when using the prototype).
You could initialize your object with a set of defaults like this:
var foo = function() {
this.defaults = {};
// copy default properties
for (var prop in foo.defaults) {
this.defaults[prop] = foo.defaults[prop];
}
this.defaults.id = 1;
};
// defaults (not on the prototype)
foo.defaults = {
id: 0,
something: "whatever",
line: 22
};
Though, in practice, it is usually easier to just code in the defaults like this:
var foo = function() {
this.options = {
id: 1,
something: "whatever",
line: 22
};
};
Related
According to this source:
To make a “real copy” (a clone) we can use... for the
so-called “shallow copy” (nested objects are copied by reference)
or a “deep cloning” function.
So, as far I get that shallow cloning assumes that if there are other inner objects inside, then they will be copied by reference. There's no cloning for them.
Well, how does copying happen for all internal properties of an object, such as descriptors, getters/setters. Are they copied by reference?
Consider the ES3-based steps taken when shallow-cloning a standard object that has just one, primitive property:
Make a new, empty object.
Add a key to that empty object.
Assign a value to that key.
Return the object.
Those steps can be accomplished using a hand-rolled function, like this one:
function es3ShallowClone(incomingObj){
var cloneOfObj = {}; // 1
for(var key in incomingObj)
cloneOfObj[key] = incomingObj[key]; // 2 and 3
return cloneOfObj; // 4
}
Using that es3ShallowClone function, you'd make clones thus:
var obj = {a:"b"};
var newObj = es3ShallowClone(obj);
Object.assign( {}, obj ) is (since the release of ES5, in 2009) a built-in way to produce the same output that is produced by es3ShallowClone:
let newAssignedObj = Object.assign( {}, obj );
Object.assign copies only enumerable properties, and, Object.assign doesn't transfer prototypes.
///////////////////////////////////////////////
For more 'power:'
If you wish to clone an otherwise standard object that has a non-enumerable property, you'll need to use Object.getOwnPropertyDescriptors and Object.defineProperty; possibly in concert with Object.create.
The ES5-based steps taken when manually shallow-cloning an otherwise standard object that has a non-enumerable property:
Make a new, empty object.
Get an array containing the descriptor from the incoming object.
"Shallow-clone" a descriptor, then apply the descriptor clone your empty object.
Return the object.
For example:
function es5ShallowClone(incomingObj){
let cloneOfObj = {}; // 1
let descriptors = Object.getOwnPropertyDescriptors(incomingObj); // 2
for(var key in descriptors)
Object.defineProperty( cloneOfObj, key, descriptors[key] ); // 3
return cloneOfObj; // 4
}
First, let's make an example object that has a non-enumerable property:
Make a descriptor:
let someDescriptor = {
'a': {
value: 'b',
writable: true,
configurable:true,
enumerable:false
};
Create an object and assign the descriptor to it:
let obj2 = Object.create( {}, someDescriptor );
Then just clone it:
let newObj2 = es5ShallowClone(obj2);
In place of es5ShallowClone, you could instead write:
let newObjToo = Object.create( {}, Object.getOwnPropertyDescriptors(obj2) );
///////////////////////////////////////////////
For even more 'power,' you'll want to transfer prototypes as well; note that my use of the word “transfer” is a bit clumsy since the prototype doesn't leave the original object... both objects end up referencing the same prototype.
The only change we need make to our es5ShallowClone function is to step 1; so that it creates an object based on the prototype of the incoming object:
function es5ShallowCloneWithPrototype(incomingObj){
let cloneOfObj = new incomingObj.constructor(); // 1
let descriptors = Object.getOwnPropertyDescriptors(incomingObj); // 2
for(var key in descriptors)
Object.defineProperty( cloneOfObj, key, descriptors[key] ); // 3
return cloneOfObj; // 4
}
First, we'll define a constructor that can make an object that has a non-enumerable property and a prototype:
function objConstructor(){
let someDescriptor = {
'a': {
value: 'b',
writable: true,
configurable:true,
enumerable:false
};
}
let returnObj = Object.create( {}, someDescriptor );
}
objConstructor.prototype.extraInfo = “javascript rocks”;
Then we'll use that constructor function to make a fancy new object:
let constructedObj = new objConstructor();
Now, we can clone that constructedObj, in all of its glory, thus:
let newCon = es5ShallowCloneWithPrototype(constructedObj);
Or, we could clone our constructedObj, in all it's glory, using the built-in magic brought to us by ES5:
let newCon2 = Object.create(
Object.getPrototypeOf(constructedObj),
Object.getOwnPropertyDescriptors(constructedObj)
);
///////////////////////////////////////////////
Hopefully that little overview helps clarify that descriptors aren't treated the same way that a regular ol object would be treated during a cloning process.
To have a closer look at the info that is available to a cloning function in ES3 or since ES5, and to see how getters and object-values are presented during enumeration, check out following codepen link, and open your browser's console... you may want to clear the console and then click the run button again to see the best representation of the captured info. PS: using ES3-style cloning, the names of setters are added to your clone and hold the value of undefined. When using ES5-style cloning, getters and setters can cause bugs if those getters or setters reference values in non-cloner accessible scopes.)
https://codepen.io/Ed_Johnsen/pen/GRmjajr
All good things.
Why can't I assign new properties to non-frozen object, which has frozen prototype:
Working without Object.freeze:
'use strict'
//This object will be prototype of next objects
var defaults = {
name: 'def name',
sections: {
1: {
secName: 'def sec name'
}
}
};
//So we have an empty object with prototype set to our default object.
var specificObject = Object.create(defaults);
specificObject.sections = {};
console.log(specificObject.hasOwnProperty('sections')); //true
specificObject.sections['1'] = Object.create(defaults.sections['1']);
Above code works as expected, but I want to make sure that defaults won't be accidentally changed. So I want to freeze my defaults object:
'use strict'
//This object will be prototype of next objects
var defaults = {
name: 'def name',
sections: {
1: {
secName: 'def sec name'
}
}
};
//!!!!!!!!!!!!
Object.freeze(defaults);
//So we have an empty object with prototype set to our default object.
var specificObject = Object.create(defaults);
//TypeError: Cannot assign to read only property 'sections' of #<Object>
specificObject.sections = {};
console.log(specificObject.hasOwnProperty('sections')); //true
specificObject.sections['1'] = Object.create(defaults.sections['1']);
What I don't get is why can't I assign to specificObject if its prototype is frozen?
//EDIT:
Notice that specific object is not frozen:
'use strict'
//This object will be prototype of next objects
var protoObj = {a: 1, o: {}};
Object.freeze(protoObj);
console.log(Object.isFrozen(protoObj)); //true
var n = Object.create(protoObj);
console.log(Object.isFrozen(n)); //false
What I don't get is why can't I assign to specificObject if its prototype is frozen?
Because property attributes are inherited. Yes, it's odd.
If you freeze an object, it will set the [[writable]] attribute of all data properties to false.
If you assign to an object property, but that property does not exist on the object, it will go and look it up on the prototype - it might be defined as setter there. When this lookup will return and say that there is a property of that name but it is non-writable, your assignment will fail (and throw in strict mode).
What can you do against this?
Use Object.defineProperty instead of assignment, it doesn't check the prototype
or similarly, use the second parameter of Object.create to create the own property
freeze the defaults only after you've assigned to specificObject.
my understanding is that specificObject.sections is pointing to its' prototype which is defaults and it is frozen object. You define a new object {} but you try to assign it to defaults.sections. SpecificObject.sections is pointing exactly there.
If you create new ownProperty on specificObject it will work:
'use strict'
//This object will be prototype of next objects
var defaults = {
name: 'def name',
sections: {
1: {
secName: 'def sec name'
}
}
};
//!!!!!!!!!!!!
Object.freeze(defaults);
//So we have an empty object with prototype set to our default object.
var specificObject = Object.create(defaults);
// this will create new property
Object.defineProperty(specificObject, 'sections',{
enumerable: true,
writable: true,
configurable: true,
value: {}
});
console.log(specificObject.hasOwnProperty('sections')); //true
specificObject.sections['1'] = Object.create(defaults.sections['1']);
explanation:
if you try to access obj.prop = val then javascript looks into obj's own properties, if not found then it looks into obj's prototype own properties. if found there then it /tries to assign val to/ lookup that property. if not found there then it tries to look into obj's prototype's prototype and so on. If prop is not found in the prototype tree then it creates new own property on obj and assigns val.
Therefore if prop is find on prototype and it is frozen you will get type error. Hope it brings some light.:)
EDIT:
as correctly pointed out by #KubaWyrostek specificObj.sections = {} will create new own property of specific Object, does not assign new value to the prototype's property, but it probably does the lookup for the property to check the writability, in that case it will run into frozen object. I didn't know about this before.
In the following example i have a simple Spy object. I wanted to create another object setting the prototype with the original object, so I've used Object.create().
Now I have a new object, which have just some properties from the original ('code' and 'breath' method). All the other properties (objects - 'name' and arrays - 'enemies') are in the _proto_ object, which i can use, because they are delegated to the original object. So far so good.
The tricky part is that if I change anything in the AnotherSpy included in the _proto_ object (the object name for example), those changes will be reflected in all the objects created from the original spy, including himself!
I also tried create a new object with using JSON.parse(), but in this way I have a new object which only have access to the 2 things that were previously in the _proto_ object - the array of enemies and the name object, without being able to use any methods of the original object (the 'breath' method).
let Spy = {
code: '007',
enemies: ['Dr.No'],
fullName: {
firstName: 'James',
lastName: 'Bond'
},
breath: function() {
console.log('im breathing..')
}
}
// original Spy breathing
Spy.breath(); // ok, he breaths
// create a new object with Object.create()
let OtherSpy = Object.create(Spy);
console.log(OtherSpy) // have direct access to properties 'code' and function 'breath' and all the others throught the __proto__ object
// Make OtherSpy breath
OtherSpy.breath(); // ok, he is breathing
// so far so good. Lets change the property and function on the OtherSpy
OtherSpy.code = '008';
OtherSpy.breath = () => {
console.log('im a new breathing')
};
OtherSpy.breath(); // ok, he's breathing differently
console.log(Spy.code); // 007 ok, original spy has the same code
Spy.breath() // ok, he stills breath in the same way.
// change the object 'name' of the OtherSpy
OtherSpy.fullName.firstName = 'Enemy';
// That change will reflect also on the original Spy...
console.log(Spy.fullName.firstName); // Enemy !!!!
// Trying in another way:
let NewSpy = JSON.parse(JSON.stringify(Spy));
console.log('NewSpy')
console.log(NewSpy) // now i dont have access to methods in the original object
NewSpy.breath() // Uncaught TypeError: NewSpy.breath is not a function
It seems that all properties included in the _proto_ object are shared in all objects that use that prototype chain.
Aside from this tricky parts that would greatly appreciate an explanation, I would like to know the proper way to create an object in JavaScript (without using ES6 classes) in order to get the advantage of the prototype delegation
and to be able to modify the properties and functions of the derived object without messing up with the original object nor any other derived objects.
Thanks in advance!
Nested properties are somewhat unuseful, so you may flatten it through using getters/setters:
const Spy = {
firstName: "Agent",
lastName: "Unnamed",
breath(){
console.log(`${this.fullName} is breathing`);
},
get fullName(){
return this.firstName + " " + this.lastName;
},
set fullName(name){
const [first, last] = name.split(" ");
this.firstName = first;
this.lastName = last;
}
};
const james = Object.create(Spy);
james.fullName = "James Bond";
james.breath();
console.log(james.fullName, james.firstName, james.lastName);
Another way would be to construct the name object inside of an constructor:
function Spy(name, code, enemies){
this.name = (([first, last]) => ({first, last}))(name.split(" "));
this.name.toString = () => name;
this.code = code;
this.enemies = enemies;
}
Spy.prototype = {
breath(){
console.log(`${this.name} is breathing`);
}
}
Usable as:
const james = new Spy("James Bond", "007", ["Dr. No"]);
james.breath();
console.log(james.name, "" + james.name);
Consider the following:
var obj1 = {"value":"one"};
var obj2 = obj1;
console.log(obj2.value+"\n"); // prints "one"
obj1 = {"value":"two"};
console.log(obj2.value+"\n"); // still prints "one"
I understand the reason for this, in the first two lines, obj1 and obj2 are references which both point to the same object, {"value":"one"}, somewhere in memory. When obj1 is assigned to a different object, {"value":"two"}, obj2 is still pointing to the same object {"value":"one"} in memory.
What I am looking for is a way to coerce the {"value":"one"} object in memory to "redirect" its callers to the {"value":"two"} object. In other words, I am looking for a way to manipulate the {"value":"one"} object so that the obj2 variable would ultimately point to the {"value":"two"} object, without reassigning the obj2 variable:
var obj1 = {"value":"one"};
var obj2 = obj1;
console.log(obj2.value+"\n"); // prints "one"
// ...some code to manipulate the `{"value":"one"}` object in memory
// so that *any* references which originally pointed to the
// `{"value":"one"}` object now point to the `{"value":"two"}`
// object, like a sort of "redirection". This would be done
// without ever explicitly reassigning the references.
console.log(obj2.value+"\n"); // now prints "two"
Is there a way to accomplish this?
The actual application involves some pretty complex Mozilla code which would encumber this thread to try and explain, so I am asking this as a general theory question.
EDIT: CONCLUSION:
"No" is the most correct answer to the actual question, torazaburo's comment below states this well. However I felt that Patrick's answer, using a proxy, comes the closest to accomplishing this, so I accepted his answer. I will add that while proxies are very powerful tools, they are not the same as the actual target object, and there are some limitations.
Check out proxies. You could use the get and set to dynamically reference a base object of your choosing, exposing proxies as the free-floating references you want to implicitly update. Here's an example:
function Proxyable(target) {
this.target = target;
this.proxy = new Proxy(this, Proxyable.handler);
}
Proxyable.prototype.getReference = function () {
return this.proxy;
};
Proxyable.prototype.setReference = function (target) {
this.target = target;
};
Proxyable.handler = {
get: function (proxyable, property) {
return proxyable.target[property];
},
set: function (proxyable, property, value) {
return proxyable.target[property] = value;
}
};
// original object
var original = { value: ['one'] };
// have to create a namespace unfortunately
var nsp = new Proxyable(original);
// reference the ref value of the namespace
var ref1 = nsp.getReference();
var ref2 = nsp.getReference();
// same references (not just values)
console.log(ref1.value === original.value);
console.log(ref2.value === original.value);
// hot-load a replacement object over the original
var replacement = { value: ['two'] };
// into the namespace
nsp.setReference(replacement);
// old references broken
console.log(ref1.value !== original.value);
console.log(ref2.value !== original.value);
// new references in place
console.log(ref1.value === replacement.value);
console.log(ref2.value === replacement.value);
I think this answer will guide you in the best way to handle this behavior. It's not possible to simply redirect an object to another object, but you can simply modify its values so that it matches the object you're trying to change.
You could also define a global object:
var objs = {
1: {value: 'test'}
2: {value: 'test2'}
};
And from there, pass around an object with the value of the key you're trying to mock. Simply change the key, and then refer to the new element.
An example:
var obj = {key: 1};
console.log(objs[obj.key]);
//Outputs: {value: 'test'}
obj.key = 2;
console.log(objs[obj.key]);
//Outputs: {value: 'test2'}
There's no way directly to do what you want. For the very reason you've stated in your question - that's just how variables and values work in javascript.
However, your very own words in your question hints at a way to achieve something that may work. But depending on the code you're dealing with it may not be what you want.
You can simply wrap the object in another reference. So that changing the content of the reference changes the object pointed to by all variables sharing the reference. You may use either of the two reference containers available: objects or arrays:
// This works:
var obj1 = [{value:'one'}];
var obj2 = obj1;
var obj1[0] = {value:'two'};
console.log(obj1[0]); // prints {"value":"two"}
console.log(obj2[0]); // prints {"value":"two"}
Alternatively:
// This also works:
var obj1 = {obj:{value:'one'}};
var obj2 = obj1;
var obj1.obj = {value:'two'};
console.log(obj1.obj); // prints {"value":"two"}
console.log(obj2.obj); // prints {"value":"two"}
in following code I declare two objects of class "person".
problem is that when for one of the variables ("Courses") I use push method to update its values so they are copied in proto and as a consequence both objects share same "Courses" array inside there proto. i want them to have there own unique arrays.
var person = {
Name: "John",
Grade: 0,
Courses:[],
setGrade : function(y){
this.Grade=y;
},
setCourse:function(t){
this.Courses.push(t);
},
}
var grade9 = Object.create(person);
grade9.setCourse("eng");
grade9.setCourse("math");
grade9.setGrade(9);
var grade10 = Object.create(person);
grade10.setCourse("phy");
grade10.setCourse("chem");
grade10.setCourse("bio");
grade10.setGrade(10);
debug output
thanx.
Create a factory method, and inside overshadow the property with a specific property for the current instance:
function createNewPerson() {
return Object.create(person, { // object.create with propertiesObject to overshadow original Courses property
Courses: { writable: true, configurable: true, value: [] }
});
}
var grade9 = createNewPerson();
grade9.setCourse("eng");
grade9.setCourse("math");
grade9.setGrade(9);
var grade10 = createNewPerson();
grade10.setCourse("phy");
grade10.setCourse("chem");
grade10.setCourse("bio");
grade10.setGrade(10);
Object.create returns an object with the prototype property set to the passed-in object. The 'instance' objects delegate to the prototype. The same prototype. In C terms they all have pointers to the same struct (the prototype) and modifying it changes the value for all the 'instance' objects (they're only pointing to it). While that isn't totally accurate its enough so for our purposes here. If you want them to all to have independent copies you'll have to add them:
personFactory = function() {
newPerson = Object.create(person);
newPerson.array = [];
}
myPerson = personFactory();