up
This commit is contained in:
parent
ab9ab64bd5
commit
97c8f22bbb
289 changed files with 195 additions and 172 deletions
|
@ -0,0 +1,311 @@
|
|||
|
||||
# Property flags and descriptors
|
||||
|
||||
As we know, objects can store properties.
|
||||
|
||||
Till now, a property was a simple "key-value" pair to us. But an object property is actually more complex and tunable thing.
|
||||
|
||||
[cut]
|
||||
|
||||
## Property flags
|
||||
|
||||
Object properties, besides a **`value`**, have three special attributes (so-called "flags"):
|
||||
|
||||
- **`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 any time.
|
||||
|
||||
First, let's see how to get those flags.
|
||||
|
||||
The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
|
||||
|
||||
The syntax is:
|
||||
```js
|
||||
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
|
||||
```
|
||||
|
||||
`obj`
|
||||
: The object to get information from.
|
||||
|
||||
`propertyName`
|
||||
: The name of the property.
|
||||
|
||||
The returned value is a so-called "property descriptor" object: it contains the value and all the flags.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
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](mdn:js/Object/defineProperty).
|
||||
|
||||
The syntax is:
|
||||
|
||||
```js
|
||||
Object.defineProperty(obj, propertyName, descriptor)
|
||||
```
|
||||
|
||||
`obj`, `propertyName`
|
||||
: The object and property to work on.
|
||||
|
||||
`descriptor`
|
||||
: Property descriptor to apply.
|
||||
|
||||
If the property exist, it updates its flags, otherwise, it creates the property with the given value and flags. Please note, that if a flag is not supplied, it is assumed `false`.
|
||||
|
||||
For instance, here a property `name` is created with all falsy flags:
|
||||
|
||||
```js run
|
||||
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 to set them to `true` in the `descriptor`.
|
||||
|
||||
Now let's see effects of the flags by example.
|
||||
|
||||
## Read-only
|
||||
|
||||
Let's make `user.name` read-only by changing `writable` flag:
|
||||
|
||||
```js run
|
||||
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 he applies his own `defineProperty` to override ours.
|
||||
|
||||
Here's the same operation, but for the case when a property doesn't exist:
|
||||
|
||||
```js run
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
## Non-enumerable
|
||||
|
||||
Now let's 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`.
|
||||
|
||||
...But 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:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
toString() {
|
||||
return this.name;
|
||||
}
|
||||
};
|
||||
|
||||
// By default, both our properties are listed:
|
||||
for(let key in user) alert(key); // name, toString
|
||||
|
||||
Object.defineProperty(user, "toString", {
|
||||
*!*
|
||||
enumerable: false
|
||||
*/!*
|
||||
});
|
||||
|
||||
*!*
|
||||
// Now toString disappears:
|
||||
*/!*
|
||||
for(let key in user) alert(key); // name
|
||||
```
|
||||
|
||||
Non-enumerable properties are also excluded from `Object.keys`:
|
||||
|
||||
```js
|
||||
alert(Object.keys(user)); // name
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Non-configurable
|
||||
|
||||
The non-configurable flag (`configurable:false`) is usually set for built-in objects and properties.
|
||||
|
||||
A non-configurable property can not be deleted or altered with `defineProperty`.
|
||||
|
||||
For instance, `Math.PI` is both read-only, non-enumerable and non-configurable:
|
||||
|
||||
```js run
|
||||
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.
|
||||
|
||||
```js run
|
||||
Math.PI = 3; // Error
|
||||
|
||||
// delete Math.PI won't work either
|
||||
```
|
||||
|
||||
Making a property non-configurable is 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:
|
||||
|
||||
```js run
|
||||
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
|
||||
*/!*
|
||||
```
|
||||
|
||||
```smart header="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.
|
||||
```
|
||||
|
||||
## Object.defineProperties
|
||||
|
||||
There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once.
|
||||
|
||||
The syntax is:
|
||||
|
||||
```js
|
||||
Object.defineProperties(obj, {
|
||||
prop1: descriptor1,
|
||||
prop2: descriptor2
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
For instance:
|
||||
|
||||
```js
|
||||
Object.defineProperties(user, {
|
||||
name: { value: "John", writable: false },
|
||||
surname: { value: "Smith", writable: false },
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
So, we can set many properties at once.
|
||||
|
||||
## Object.getOwnPropertyDescriptors
|
||||
|
||||
To get many descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors).
|
||||
|
||||
Together with `Object.defineProperties` it can be used as an "flags-aware" way of cloning an object:
|
||||
|
||||
```js
|
||||
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
|
||||
```
|
||||
|
||||
Normally when we clone an object, we use an assignment to copy properties, like this:
|
||||
|
||||
```js
|
||||
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.
|
||||
|
||||
## Sealing an object globally
|
||||
|
||||
Property descriptors work at the level of individual properties.
|
||||
|
||||
There are also methods that limit access to the *whole* object:
|
||||
|
||||
[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions)
|
||||
: Forbids to add properties to the object.
|
||||
|
||||
[Object.seal(obj)](mdn:js/Object/seal)
|
||||
: Forbids to add/remove properties, sets for all existing properties `configurable: false`.
|
||||
|
||||
[Object.freeze(obj)](mdn:js/Object/freeze)
|
||||
: Forbids to add/remove/change properties, sets for all existing properties `configurable: false, writable: false`.
|
||||
|
||||
And also there are tests for them:
|
||||
|
||||
[Object.isExtensible(obj)](mdn:js/Object/isExtensible)
|
||||
: Returns `false` if adding properties is forbidden, otherwise `true`.
|
||||
|
||||
[Object.isSealed(obj)](mdn:js/Object/isSealed)
|
||||
: Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`.
|
||||
|
||||
[Object.isFrozen(obj)](mdn:js/Object/isFrozen)
|
||||
: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`.
|
||||
|
||||
These methods are rarely used in practice.
|
Loading…
Add table
Add a link
Reference in a new issue