This commit is contained in:
Ilya Kantor 2019-08-03 00:01:38 +03:00
parent 25a77d376a
commit 0757d51080
2 changed files with 36 additions and 24 deletions

View file

@ -63,7 +63,7 @@ Object.defineProperty(obj, propertyName, descriptor)
``` ```
`obj`, `propertyName` `obj`, `propertyName`
: The object and property to work on. : The object and its property to apply the descriptor.
`descriptor` `descriptor`
: Property descriptor to apply. : Property descriptor to apply.
@ -116,7 +116,7 @@ Object.defineProperty(user, "name", {
}); });
*!* *!*
user.name = "Pete"; // Error: Cannot assign to read only property 'name'... user.name = "Pete"; // Error: Cannot assign to read only property 'name'
*/!* */!*
``` ```
@ -126,25 +126,24 @@ Now no one can change the name of our user, unless they apply their own `defineP
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. 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.
``` ```
Here's the same operation, but for the case when a property doesn't exist: Here's the same example, but the property is created from scratch:
```js run ```js run
let user = { }; let user = { };
Object.defineProperty(user, "name", { Object.defineProperty(user, "name", {
*!* *!*
value: "Pete", value: "John",
// for new properties need to explicitly list what's true // for new properties need to explicitly list what's true
enumerable: true, enumerable: true,
configurable: true configurable: true
*/!* */!*
}); });
alert(user.name); // Pete alert(user.name); // John
user.name = "Alice"; // Error user.name = "Pete"; // Error
``` ```
## Non-enumerable ## Non-enumerable
Now let's add a custom `toString` to `user`. Now let's add a custom `toString` to `user`.

View file

@ -34,7 +34,7 @@ let user = {
}; };
``` ```
Now we want to add a "fullName" property, that should be "John Smith". Of course, we don't want to copy-paste existing information, so we can implement it as an accessor: Now we want to add a `fullName` property, that should be `"John Smith"`. Of course, we don't want to copy-paste existing information, so we can implement it as an accessor:
```js run ```js run
let user = { let user = {
@ -55,7 +55,21 @@ alert(user.fullName); // John Smith
From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes. From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes.
As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error. As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error:
На данный момент `fullName` имеет только геттер. Если попытаться присвоить значение свойству `user.fullName`, то это вызовет ошибку:
```js run
let user = {
get fullName() {
return `...`;
}
};
*!*
user.fullName = "Test"; // Error (property has only a getter)
*/!*
```
Let's fix it by adding a setter for `user.fullName`: Let's fix it by adding a setter for `user.fullName`:
@ -84,13 +98,8 @@ alert(user.surname); // Cooper
As the result, we have a "virtual" property `fullName`. It is readable and writable, but in fact does not exist. As the result, we have a "virtual" property `fullName`. It is readable and writable, but in fact does not exist.
```smart header="Accessor properties are only accessible with get/set" ```smart header="No support for `delete`"
Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data property any more. An attempt to `delete` on accessor property causes an error.
- If there's a getter -- we can read `object.prop`, otherwise we can't.
- If there's a setter -- we can set `object.prop=...`, otherwise we can't.
And in either case we can't `delete` an accessor property.
``` ```
@ -100,7 +109,7 @@ Descriptors for accessor properties are different -- as compared with data prope
For accessor properties, there is no `value` and `writable`, but instead there are `get` and `set` functions. For accessor properties, there is no `value` and `writable`, but instead there are `get` and `set` functions.
So an accessor descriptor may have: That is, an accessor descriptor may have:
- **`get`** -- a function without arguments, that works when a property is read, - **`get`** -- a function without arguments, that works when a property is read,
- **`set`** -- a function with one argument, that is called when the property is set, - **`set`** -- a function with one argument, that is called when the property is set,
@ -132,7 +141,7 @@ alert(user.fullName); // John Smith
for(let key in user) alert(key); // name, surname for(let key in user) alert(key); // name, surname
``` ```
Please note once again that a property can be either an accessor or a data property, not both. Please note once again that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both.
If we try to supply both `get` and `value` in the same descriptor, there will be an error: If we try to supply both `get` and `value` in the same descriptor, there will be an error:
@ -151,9 +160,9 @@ Object.defineProperty({}, 'prop', {
## Smarter getters/setters ## Smarter getters/setters
Getters/setters can be used as wrappers over "real" property values to gain more control over them. Getters/setters can be used as wrappers over "real" property values to gain more control over operations with them.
For instance, if we want to forbid too short names for `user`, we can store `name` in a special property `_name`. And filter assignments in the setter: For instance, if we want to forbid too short names for `user`, we can have a setter `name` and keep the value in a separate property `_name`:
```js run ```js run
let user = { let user = {
@ -176,14 +185,16 @@ alert(user.name); // Pete
user.name = ""; // Name is too short... user.name = ""; // Name is too short...
``` ```
Technically, the external code may still access the name directly by using `user._name`. But there is a widely known agreement that properties starting with an underscore `"_"` are internal and should not be touched from outside the object. So, the name is stored in `_name` property, and the access is done via getter and setter.
Technically, external code is able to access the name directly by using `user._name`. But there is a widely known convention that properties starting with an underscore `"_"` are internal and should not be touched from outside the object.
## Using for compatibility ## Using for compatibility
One of the great ideas behind getters and setters -- they allow to take control over a "regular" data property at any moment by replacing it with getter and setter and tweak its behavior. One of the great uses of accessors -- they allow to take control over a "regular" data property at any moment by replacing it with getter and setter and tweak its behavior.
Let's say we started implementing user objects using data properties `name` and `age`: Imagine, we started implementing user objects using data properties `name` and `age`:
```js ```js
function User(name, age) { function User(name, age) {
@ -209,7 +220,9 @@ let john = new User("John", new Date(1992, 6, 1));
Now what to do with the old code that still uses `age` property? Now what to do with the old code that still uses `age` property?
We can try to find all such places and fix them, but that takes time and can be hard to do if that code is written/used by many other people. And besides, `age` is a nice thing to have in `user`, right? In some places it's just what we want. We can try to find all such places and fix them, but that takes time and can be hard to do if that code is used by many other people. And besides, `age` is a nice thing to have in `user`, right?
Let's keep it.
Adding a getter for `age` solves the problem: Adding a getter for `age` solves the problem: