This commit is contained in:
Ilya Kantor 2017-09-24 08:58:52 +03:00
parent ab13ef8685
commit 0a2b936133
5 changed files with 41 additions and 34 deletions

View file

@ -70,7 +70,7 @@ While choosing a name try to use the most abstract word. Like `obj`, `data`, `va
...But what to do if `data` is already taken? Try `value`, it's also universal. After all, a variable eventually gets a *value*. ...But what to do if `data` is already taken? Try `value`, it's also universal. After all, a variable eventually gets a *value*.
- **Name the variable by its type: `str`, `num`...** - **Name a variable by its type: `str`, `num`...**
Give them a try. A young ninja may wonder -- do such names make the code worse? Actually, yes! Give them a try. A young ninja may wonder -- do such names make the code worse? Actually, yes!
@ -78,7 +78,7 @@ While choosing a name try to use the most abstract word. Like `obj`, `data`, `va
Indeed, the value type is easy to find out by debugging. But what's the meaning of the variable? Which string/number does it store? There's just no way to figure out without a good meditation! Indeed, the value type is easy to find out by debugging. But what's the meaning of the variable? Which string/number does it store? There's just no way to figure out without a good meditation!
- **...But what if there are no more such names?** Just add a letter: `item1, item2, elem5, data1`... - **...But what if there are no more such names?** Just add a number: `data1, item2, elem5`...
## Attention test ## Attention test

View file

@ -46,7 +46,7 @@ alert('Press the "Play" button in the upper-right corner to run');
``` ```
Examples that use modern JS will work only if your browser supports it. Examples that use modern JS will work only if your browser supports it.
``` ````
```offline ```offline
As you're reading the offline version, examples are not runnable. But they usually work :) As you're reading the offline version, examples are not runnable. But they usually work :)

View file

@ -222,7 +222,11 @@ obj.__proto__ = 5;
alert(obj.__proto__); // [object Object], didn't work as intended alert(obj.__proto__); // [object Object], didn't work as intended
``` ```
As we see from the code, the assignment to a primitive `5` is ignored. If we want to store *arbitrary* (user-provided) keys, then such behavior can be the source of bugs and even vulnerabilities, because it's unexpected. There's another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in the chapter <info:map-set-weakmap-weakset>, which supports arbitrary keys. As we see from the code, the assignment to a primitive `5` is ignored.
That can be come a source of bugs and even vulnerabilies if we intent to store arbitrary key-value pairs in an object, and allow a visitor to specify the keys. In that case the visitor may choose "__proto__" as the key, and the assignment logic will be ruined (as shown above).
There's another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in the chapter <info:map-set-weakmap-weakset>, which supports arbitrary keys. Also there's a way to make objects treat `__proto__` as a regular property, but first we need to know more about objects to understand it.
```` ````

View file

@ -9,18 +9,25 @@ Till now we've only seen strings. Now let's see the advantages that symbols can
## Symbols ## Symbols
"Symbol" value represents an unique identifier with a given name. "Symbol" value represents an unique identifier.
A value of this type can be created using `Symbol(name)`: A value of this type can be created using `Symbol()`:
```js ```js
// id is a symbol with the name "id" // id is a new symbol
let id = Symbol();
```
We can also give symbol a description (also called a symbol name), mostly useful for debugging purposes:
```js
// id is a symbol with the description "id"
let id = Symbol("id"); let id = Symbol("id");
``` ```
Symbols are guaranteed to be unique. Even if we create many symbols with the same name, they are different values. Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn't affect anything.
For instance, here are two symbols with the same name -- they are not equal: For instance, here are two symbols with the same description -- they are not equal:
```js run ```js run
let id1 = Symbol("id"); let id1 = Symbol("id");
@ -56,13 +63,11 @@ alert(id.toString()); // Symbol(id), now it works
That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not occasionally convert one into another. That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not occasionally convert one into another.
```` ````
## "Hidden" properties ## "Hidden" properties
Symbols allow us to create "hidden" properties of an object, that no other part of code can occasionally access or overwrite. Symbols allow us to create "hidden" properties of an object, that no other part of code can occasionally access or overwrite.
For instance, if we want to store an "identifier" for the object `user`, we can create a symbol with the name `id` for it: For instance, if we want to store an "identifier" for the object `user`, we can use a symbol as a key for it:
```js run ```js run
let user = { name: "John" }; let user = { name: "John" };
@ -72,11 +77,13 @@ user[id] = "ID Value";
alert( user[id] ); // we can access the data using the symbol as the key alert( user[id] ); // we can access the data using the symbol as the key
``` ```
Now let's imagine that another script wants to have its own "id" property inside `user`, for its own purposes. That may be another JavaScript library, so the scripts are completely unaware of each other. What's the benefit over using `Symbol("id")` over a string `"id"`?
No problem. It can create its own `Symbol("id")`. Let's make the example a bit deeper to see that.
The script: Imagine that another script wants to have its own "id" property inside `user`, for its own purposes. That may be another JavaScript library, so the scripts are completely unaware of each other.
Then that script can create its own `Symbol("id")`, like this:
```js ```js
// ... // ...
@ -87,7 +94,7 @@ user[id] = "Their id value";
There will be no conflict, because symbols are always different, even if they have the same name. There will be no conflict, because symbols are always different, even if they have the same name.
Please note that if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict: Now note that if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict:
```js run ```js run
let user = { name: "John" }; let user = { name: "John" };
@ -176,25 +183,27 @@ alert( obj[0] ); // test (same property)
## Global symbols ## Global symbols
As we've seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be the same entities. As we've seen, usually all symbols are different, even if they have the same names. But sometimes we want same-named symbols to be same entities.
For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property. For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property.
To achieve that, there exists a *global symbol registry*. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol. To achieve that, there exists a *global symbol registry*. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol.
In order to create or read a symbol in the registry, use `Symbol.for(name)`. In order to create or read a symbol in the registry, use `Symbol.for(key)`.
That call checks the global registry, and if there's a symbol described as `key`, then returns it, otherwise creates a new symbol `Symbol(key)` and stores it in the registry by the given `key`.
For instance: For instance:
```js run ```js run
// read from the global registry // read from the global registry
let name = Symbol.for("name"); // if the symbol did not exist, it is created let id = Symbol.for("id"); // if the symbol did not exist, it is created
// read it again // read it again
let nameAgain = Symbol.for("name"); let idAgain = Symbol.for("id");
// the same symbol // the same symbol
alert( name === nameAgain ); // true alert( id === idAgain ); // true
``` ```
Symbols inside the registry are called *global symbols*. If we want an application-wide symbol, accessible everywhere in the code -- that's what they are for. Symbols inside the registry are called *global symbols*. If we want an application-wide symbol, accessible everywhere in the code -- that's what they are for.
@ -207,7 +216,7 @@ In JavaScript, as we can see, that's right for global symbols.
### Symbol.keyFor ### Symbol.keyFor
For global symbols, not only `Symbol.for(name)` returns a symbol by name, but there's a reverse call: `Symbol.keyFor(sym)`, that does the reverse: returns a name by a global symbol. For global symbols, not only `Symbol.for(key)` returns a symbol by name, but there's a reverse call: `Symbol.keyFor(sym)`, that does the reverse: returns a name by a global symbol.
For instance: For instance:
@ -220,20 +229,16 @@ alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id alert( Symbol.keyFor(sym2) ); // id
``` ```
The `Symbol.keyFor` internally uses the global symbol registry to look up the name for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and return `undefined`. The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and return `undefined`.
For instance: For instance:
```js run ```js run
alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, non-global symbol alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol
``` ```
So, for global symbols the name may be indeed helpful, as we can get a symbol by id.
And for non-global symbols the name is only used for debugging purposes, like printing out a symbol.
## System symbols ## System symbols
There exist many "system" symbols that JavaScript uses internally, and we can use them to fine-tune various aspects of our objects. There exist many "system" symbols that JavaScript uses internally, and we can use them to fine-tune various aspects of our objects.
@ -254,9 +259,9 @@ Other symbols will also become familiar when we study the corresponding language
`Symbol` is a primitive type for unique identifiers. `Symbol` is a primitive type for unique identifiers.
Symbols are created with `Symbol(name)` call. Symbols are created with `Symbol()` call with an optional description.
Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: `Symbol.for(name)` returns (creates if needed) a global symbol with the given name. Multiple calls of `Symbol.for` return exactly the same symbol. Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: `Symbol.for(key)` returns (creates if needed) a global symbol with `key` as the name. Multiple calls of `Symbol.for` return exactly the same symbol.
Symbols have two main use cases: Symbols have two main use cases:

View file

@ -1,6 +1,6 @@
# F.prototype # F.prototype
In modern JavaScript we can set a prototype using `__proto__`. But it wasn't like that all the time. In modern JavaScript we can set a prototype using `__proto__`, as described in the previous article. But it wasn't like that all the time.
[cut] [cut]
@ -10,9 +10,7 @@ But in the old times, there was another (and the only) way to set it: to use a `
## The "prototype" property ## The "prototype" property
As we know already, `new F()` creates a new object. But what we didn't use yet `F.prototype` property. As we know already, `new F()` creates a new object. In the process, its "magic" `F.prototype` property is used by the JavaScript itself to set `[[Prototype]]` for new objects.
That property is used by the JavaScript itself to set `[[Prototype]]` for new objects.
**When a new object is created with `new F()`, the object's `[[Prototype]]` is set to `F.prototype`.** **When a new object is created with `new F()`, the object's `[[Prototype]]` is set to `F.prototype`.**