This commit is contained in:
Ilya Kantor 2019-07-29 00:12:34 +03:00
parent 3ba28aa104
commit 34e9cdca36
10 changed files with 55 additions and 62 deletions

View file

@ -322,7 +322,7 @@ alert( *!*key*/!* in user ); // true, takes the name from key and checks for suc
```
````smart header="Using \"in\" for properties that store `undefined`"
Usually, the strict comparison `"=== undefined"` check works fine. But there's a special case when it fails, but `"in"` works correctly.
Usually, the strict comparison `"=== undefined"` check the property existance just fine. But there's a special case when it fails, but `"in"` works correctly.
It's when an object property exists, but stores `undefined`:
@ -569,7 +569,7 @@ user.age = 25; // (*)
alert(user.age); // 25
```
It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes the value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`.
It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes only value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`.
The `const` would give an error if we try to set `user` to something else, for instance:

View file

@ -192,9 +192,7 @@ alert( obj[0] ); // test (same property)
## 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 same entities.
For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property.
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 same entities. 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.
@ -230,22 +228,29 @@ For global symbols, not only `Symbol.for(key)` returns a symbol by name, but the
For instance:
```js run
// get symbol by name
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// get name from symbol
// get name by symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
```
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`.
That said, any symbols have `description` property.
For instance:
```js run
alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol
alert( Symbol.keyFor(globalSymbol) ); // name, global symbol
alert( Symbol.keyFor(localSymbol) ); // undefined, not global
alert( localSymbol.description ); // name
```
## System symbols
@ -281,4 +286,4 @@ Symbols have two main use cases:
2. There are many system symbols used by JavaScript which are accessible as `Symbol.*`. We can use them to alter some built-in behaviors. For instance, later in the tutorial we'll use `Symbol.iterator` for [iterables](info:iterable), `Symbol.toPrimitive` to setup [object-to-primitive conversion](info:object-toprimitive) and so on.
Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in methods and syntax constructs adhere to a common agreement that they are. And the one who explicitly calls the aforementioned methods probably understands well what he's doing.
Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in functions and syntax constructs don't use these methods.

View file

@ -111,6 +111,7 @@ let user = {
sayHi() {
*!*
// "this" is the "current object"
alert(this.name);
*/!*
}
@ -176,7 +177,7 @@ function sayHi() {
}
```
The value of `this` is evaluated during the run-time, depending on the context. And it can be anything.
The value of `this` is evaluated during the run-time, depending on the context.
For instance, here the same function is assigned to two different objects and has different "this" in the calls:

View file

@ -15,9 +15,7 @@ In the chapter <info:type-conversions> we've seen the rules for numeric, string
We can fine-tune string and numeric conversion, using special object methods.
The conversion algorithm is called `ToPrimitive` in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive). It's called with a "hint" that specifies the conversion type.
There are three variants:
There are three variants of type conversion, so-called "hints", described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive):
`"string"`
: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`:
@ -66,7 +64,7 @@ Please note -- there are only three hints. It's that simple. There is no "boolea
**To do the conversion, JavaScript tries to find and call three object methods:**
1. Call `obj[Symbol.toPrimitive](hint)` if the method exists,
1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists,
2. Otherwise if hint is `"string"`
- try `obj.toString()` and `obj.valueOf()`, whatever exists.
3. Otherwise if hint is `"number"` or `"default"`
@ -78,9 +76,9 @@ Let's start from the first method. There's a built-in symbol named `Symbol.toPri
```js
obj[Symbol.toPrimitive] = function(hint) {
// return a primitive value
// must return a primitive value
// hint = one of "string", "number", "default"
}
};
```
For instance, here `user` object implements it:
@ -138,6 +136,8 @@ alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
```
As we can see, the behavior is the same as the previous example with `Symbol.toPrimitive`.
Often we want a single "catch-all" place to handle all primitive conversions. In this case, we can implement `toString` only, like this:
```js run
@ -171,25 +171,24 @@ In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there wil
## Further operations
An operation that initiated the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary.
An operation that initiated the conversion gets the primitive, and then continues to work with it, applying further conversions if necessary.
For instance:
- Mathematical operations (except binary plus) perform `ToNumber` conversion:
- Mathematical operations, except binary plus, convert the primitive to a number:
```js run
let obj = {
toString() { // toString handles all conversions in the absence of other methods
// toString handles all conversions in the absence of other methods
toString() {
return "2";
}
};
alert(obj * 2); // 4, ToPrimitive gives "2", then it becomes 2
alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number
```
- Binary plus checks the primitive -- if it's a string, then it does concatenation, otherwise it performs `ToNumber` and works with numbers.
String example:
- Binary plus will concatenate strings in the same situation:
```js run
let obj = {
toString() {
@ -200,24 +199,12 @@ For instance:
alert(obj + 2); // 22 (ToPrimitive returned string => concatenation)
```
Number example:
```js run
let obj = {
toString() {
return true;
}
};
alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber)
```
## Summary
The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value.
There are 3 types (hints) of it:
- `"string"` (for `alert` and other string conversions)
- `"string"` (for `alert` and other operations that need a string)
- `"number"` (for maths)
- `"default"` (few operators)

View file

@ -17,8 +17,10 @@ Here's the demo of the code:
```js
let accumulator = new Accumulator(1); // initial value 1
accumulator.read(); // adds the user-entered value
accumulator.read(); // adds the user-entered value
alert(accumulator.value); // shows the sum of these values
```

View file

@ -215,6 +215,8 @@ john = {
*/
```
To create complex objects, there's a more advanced syntax, [classes](info:classes), that we'll cover later.
## Summary
- Constructor functions or, briefly, constructors, are regular functions, but there's a common agreement to name them with capital letter first.