Ok I am killing my brain here I have an array
var myArray = ['Bob', 'Sue', 'Jim'];
myArray.__proto__ = new Entity();
//Entity looks something like this
Entity = function(){
this.isChanged = false;
this.add = function(newPerson){
alert(this.length); //alerts with 3
alert(JSON.stringify(this)); //alerts a {}
this.push(newPerson);
this.isChanged = true;
}
}
push does not exist on an object but its obviously an array as per the alert returning a 3.
very curious how to access my array that seems to be wrapped by an object thanks to my proto
how to access my array that seems to be wrapped by an object thanks to my __proto__
It is not wrapped - it just lost it's identity due to your modification of __proto__. The array now inherits from your new Entity instance instead of from Array.prototype.
If you want to call Array methods on it, you will have to do it using .call:
Array.prototype.push.call(this, newPerson);
However, your implementation of inheritance is questionable anyway. Even if you use an array object and mutate its [[prototype]], you rather should be doing
var myArray = new Entitiy(['Bob', 'Sue', 'Jim']);
// Entity looks like this
function Entity(arr) {
if (!Array.isArray(arr)) {
arr = [];
// maybe:
// arr.push.apply(arr, arguments);
}
arr.__proto__ = Entity.prototype;
arr.isChanged = false;
return arr;
}
Entity.prototype = Object.create(Array.prototype);
Entity.prototype.constructor = Entity;
Entity.prototype.add = function(newPerson) {
alert(this.length); //alerts with 3
alert(JSON.stringify(this)); //alerts a ["Bob","Sue","Jim"]
this.push(newPerson);
this.isChanged = true;
};
The array isn't wrapped, it's no longer an array! __proto__ is a depricated getter/setter pair to access an objects internal [[Prototype]]. Since you assign a value the setter is used and you simply overwrite its complete prototype with an instance of Entity. That's why push() (and all others: pop(), splice(), ... ) doesn't exist any longer.
Why alert(this.length);works ? length is not a property of Array.prototype but an own property of each Array instance. So it's not overwritten/removed by changing the prototype, your "thing" still has a length. You can check that with following:
console.log(Object.getOwnPropertyNames(myArray)); // --> ['0', '1', '2', 'length']
Of course you can access the properties of your "thing", e.g. console.log(myArray[1]) // --> 'Sue'or assign new properties, but you have to use object-methods for it. So if inside Entity() instead of this.push(newPerson) you use this[this.length] = newPerson it will work.
Reference for __proto__ here.
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.
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);
Here I have a simple code to understand how Object.create() works. Here I have a common object to be used as prototype which is prototypeObj.newObj is an object which has its prototype set to prototypeObj. Here I have another object called session which has a property called anotherObj, and anotherObj has the same prototype as newObj. But adding new value to a property called foo which resides in the prototype of anotherObj , affect newObj too. Why am I experiencing this behaviour?
Code:
var prototypeObj = {
foo: [],
addItemToFoo: function(add) {
this.foo.push(add);
}
}
function create(fooVal) {
var myProto = Object.create(prototypeObj);
myProto.foo = fooVal;
return myProto;
}
var newObj = create([1, 2]); // initialized with [1,2]
session = {
anotherObj: create(newObj.foo) // initialized with [1,2]
}
session.anotherObj.addItemToFoo(6); // pushed 6 to session.anotherObj.foo
console.log("newObj.foo is " + newObj.foo); // newObj also get 6 pushed to its foo property
console.log("anotherObj.foo is " + session.anotherObj.foo);
foo is an array, it works by reference.
anotherObj: create(newObj.foo)
You are copying the reference here, so both your old and new object will have the same array reference to insert elements in. If you want to have to different array references, you should first copy it like this create(newObj.foo.slice())
https://jsfiddle.net/x8ftnh82/
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();
Kind of strange thing is happening with my code. What I observe is something like this:
var prototype = {
property : "",
array : []
}
var objectArray = [];
function myFunction {
objectArray[0] = Object.create(prototype);
objectArray[1] = Object.create(prototype);
objectArray[0].property = "Hello 1";
objectArray[0].array.push("Hello 1");
objectArray[1].property = "Hello 2";
objectArray[1].array.push("Hello 2");
// At this point when running the program I get:
// objectArray[0].property == "Hello 1"
// objectArray[1].property == "Hello 2";
// objectArray[1].array == objectArray[1].array == prototype.array
// == ["Hello 1", "Hello 2"]
}
What I want, and expected, was two separate arrays for the two objects. What am I missing here?
In JavaScript, objects are copied by reference, so both objectArray objects are simply references to the same object ( prototype ). You need to clone the object or create instances using the new keyword and a constructor to create separate objects.
Example on how to do it using the new keyword:
var prototype = function() {
this.property = "";
this.array = [];
};
objectArray[0] = new prototype();
objectArray[1] = new prototype();
You can also do:
var prototypeFactory = function() {
return {
property: "",
array: []
};
};
objectArray[0] = prototypeFactory();
objectArray[1] = prototypeFactory();
The prototype object exists the same as the [[Prototype]] for each object. They don't get a fresh copy when you use Object.create().
You'd need to use assignment, which never walks the prototype chain.
I wonder if you aren't asking yourself "why does it work for property, but not for array?". The fact is, it doesn't work for property either, JavaScript is fooling you.
When objects share the same prototype, one must consider that:
All properties of the prototype are shared by the objects that inherit from that prototype.
You cannot assign to properties of the prototype, only read them (unless it's an accessor property, but let's keep that aside).
So what's actually happening here:
objectArray[0].property = "Hello 1";
objectArray[1].property = "Hello 2";
is that a new own property called "property" is being created on each object, while prototype.property remains untouched. The array property is behaving differently because you're not assigning to it, you're accessing prototype.array and calling the push method on it.
Don't forget that Object.create() isn't yet standardized, and won't work in IE8 or FF3.6. A simple work-around to clone an object is to use the JSON object:
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}