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:
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:
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.