Copying Accessor Properties
Version: ES2015 (Object.assign), ES2017 (Object.getOwnPropertyDescriptors)
Level: Intermediate
In using Object.assign()
to copy properties into an object, you’re assigning new properties much in the way that you would if you were to use the assignment operator (=
). Each new property is added as a simple data property, even if the original property value is coming from an accessor property. In other words, Object.assign()
does not copy property implementations, only property values.
let source = {
data: 1,
get accessor () {
return 2
}
};
let target = {};
Object.assign(target, source);
console.log(target.data); // 1
console.log(target.accessor); // 2
console.log(Object.getOwnPropertyDescriptor(source, 'accessor').get);
// function
console.log(Object.getOwnPropertyDescriptor(target, 'accessor').get);
// undefined (not accessor, only data property)
If you want to copy properties from an object and keep them accessors if they are accessors, Object.assign()
won’t cut it. Instead what you’ll want to use Object.defineProperty()
, or more likely, in the case of copying multiple properties at once, Object.defineProperties()
. This combined with Object.getOwnPropertyDescriptor()
or Object.getOwnPropertyDescriptors()
will let you copy accessor properties as accessor properties. Additionally, it will maintain other property characteristics such as writable, enumerable, and configurable.
let source = {
data: 1,
get accessor () {
return 2
}
};
let target = {};
Object.defineProperties(
target,
Object.getOwnPropertyDescriptors(source)
);
console.log(target.data); // 1
console.log(target.accessor); // 2
console.log(Object.getOwnPropertyDescriptor(source, 'accessor').get);
// function
console.log(Object.getOwnPropertyDescriptor(target, 'accessor').get);
// function (accessor!)
Bear in mind that when accessor functions are copied this way, both objects will be using the same accessor functions. This can cause problems if the function is using some hidden data such as a closure variable to represent the property’s value.
let vault;
{
let secretPassword = '12345'; // only accessible to password
vault = {
get password () {
return secretPassword;
},
set password (value) {
secretPassword = value;
}
}
};
let lockbox = {};
Object.defineProperties(
lockbox,
Object.getOwnPropertyDescriptors(vault)
);
console.log(vault.password); // 12345
lockbox.password = 'abcde';
console.log(lockbox.password); // abcde
console.log(vault.password); // abcde (should not have changed!)
However, if the accessor is using another, non-hidden data property for its data, everything will work ok since the function will pull from the instance for its data rather than something that would be shared between both instances.
let vault = {
_password: '12345', // not so secret now
get password () {
return this._password;
},
set password (value) {
this._password = value;
}
};
let lockbox = {};
Object.defineProperties(
lockbox,
Object.getOwnPropertyDescriptors(vault)
);
console.log(vault.password); // 12345
lockbox.password = 'abcde';
console.log(lockbox.password); // abcde
console.log(vault.password); // 12345 (Ok, unchanged)
More info:
- Object.assign on MDN
- Object.defineProperty on MDN
- Object.defineProperties on MDN
- Object.getOwnPropertyDescriptor on MDN
- Object.getOwnPropertyDescriptors on MDN
More tips: JavaScript Tips of the Day