When you add a new property to an object, besides value, three flags are set with it.
writable if true, can be changed, otherwise it’s read-only.enumerable if true, then listed in loops, otherwise not listed.configurable if true, the property can be deleted and these attributes can be modified, otherwise not.We didn’t see them yet, because generally they do not show up. When we create a property “the usual way”, all of them are true. But we also can change them anytime.
The method Object.getOwnPropertyDescriptor allows to query the full information about a property.
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
The returned value is a so-called “property descriptor” object: it contains the value and all the flags.
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
To change the flags, we can use Object.defineProperty.
Object.defineProperty(obj, propertyName, descriptor)
If the property exists, defineProperty updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed false.
For instance, here a property name is created with all falsy flags:
let user = {};
Object.defineProperty(user, "name", {
value: "John"
});
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": "John",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
Compare it with “normally created” user.name above: now all flags are falsy. If that’s not what we want then we’d better set them to true in descriptor.
Let’s make user.name read-only by changing writable flag:
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false
});
user.name = "Pete"; // Error: Cannot assign to read only property 'name'...
Now no one can change the name of our user, unless they apply their own defineProperty to override ours.
Here’s the same operation, but for the case when a property doesn’t exist:
let user = { };
Object.defineProperty(user, "name", {
value: "Pete",
// for new properties need to explicitly list what's true
enumerable: true,
configurable: true
});
alert(user.name); // Pete
user.name = "Alice"; // Error
Now let’s add a custom toString to user.
Normally, a built-in toString for objects is non-enumerable, it does not show up in for..in. But if we add toString of our own, then by default it shows up in for..in, like this:
let user = {
name: "John",
toString() {
return this.name;
}
};
// By default, both our properties are listed:
for (let key in user) alert(key); // name, toString
If we don’t like it, then we can set enumerable:false. Then it won’t appear in for..in loop, just like the built-in one:
let user = {
name: "John",
toString() {
return this.name;
}
};
Object.defineProperty(user, "toString", {
enumerable: false
});
// Now our toString disappears:
for (let key in user) alert(key); // name
Non-enumerable properties are also excluded from Object.keys:
alert(Object.keys(user)); // name
The non-configurable flag (configurable:false) is sometimes preset for built-in objects and properties.
A non-configurable property can not be deleted or altered with defineProperty.
For instance, Math.PI is read-only, non-enumerable and non-configurable:
let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": 3.141592653589793,
"writable": false,
"enumerable": false,
"configurable": false
}
*/
So, a programmer is unable to change the value of Math.PI or overwrite it.
Math.PI = 3; // Error delete Math.PI; // Error
Making a property non-configurable is a one-way road. We cannot change it back, because defineProperty doesn’t work on non-configurable properties.
Here we are making user.name a “forever sealed” constant:
let user = { };
Object.defineProperty(user, "name", {
value: "John",
writable: false,
configurable: false
});
// won't be able to change user.name or its flags
// all this won't work:
// user.name = "Pete"
// delete user.name
// defineProperty(user, "name", ...)
Object.defineProperty(user, "name", {writable: true}); // Error
Errors appear only in use strict. In the non-strict mode, no errors occur when writing to read-only properties and such. But the operation still won’t succeed. Flag-violating actions are just silently ignored in non-strict.
There’s a method Object.defineProperties(obj, descriptors) that allows to define many properties at once.
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
For example:
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
});
To get all property descriptors at once, we can use the method Object.getOwnPropertyDescriptors(obj).
Together with Object.defineProperties it can be used as a “flags-aware” way of cloning an object:
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
Normally when we clone an object, we use an assignment to copy properties, like this:
for (let key in user) {
clone[key] = user[key]
}
…But that does not copy flags. So if we want a “better” clone then Object.defineProperties is preferred.
Another difference is that for..in ignores symbolic properties, but Object.getOwnPropertyDescriptors returns all property descriptors including symbolic ones.
The method Object.getOwnPropertyNames() returns an array of all the property. It also includes all symbols, and non-enumerable properties.
let obj = {
name : "Santanu Bera",
age : 28,
};
Object.defineProperty(obj, 'location', {
value : "Kolkata",
writable : false,
enumerable : false,
configurable : false
});
console.log(Object.getOwnPropertyNames(obj)); // ["name", "age", "location"]