work
This commit is contained in:
parent
4c531b5ae7
commit
d4c714cbe1
261 changed files with 7370 additions and 546 deletions
|
@ -490,13 +490,13 @@ alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
|
|||
|
||||
The example above demonstrates that there is only one object. Like if we had a cabinet with two keys and used one of them (`admin`) to get into it -- later using the other one (`user`) we will see things modified.
|
||||
|
||||
|
||||
|
||||
### Comparison by reference
|
||||
|
||||
The equality `==` and strict equality `===` operators for objects work exactly the same, simple way.
|
||||
The equality `==` and strict equality `===` operators for objects work exactly the same.
|
||||
|
||||
**Two object variables are equal only when reference the same object.**
|
||||
**Two objects are equal only if they are the same object.**
|
||||
|
||||
For instance, two variables reference the same object, they are equal:
|
||||
|
||||
```js run
|
||||
let a = {};
|
||||
|
@ -506,9 +506,7 @@ alert( a == b ); // true, both variables reference the same object
|
|||
alert( a === b ); // true
|
||||
```
|
||||
|
||||
In all other cases objects are non-equal, even if their content is the same.
|
||||
|
||||
For instance:
|
||||
And here two independent objects are not equal, even though both are empty:
|
||||
|
||||
```js run
|
||||
let a = {};
|
||||
|
@ -517,9 +515,7 @@ let b = {}; // two independent objects
|
|||
alert( a == b ); // false
|
||||
```
|
||||
|
||||
That rule only applies to object vs object equality checks.
|
||||
|
||||
For other comparisons like whether an object less/greater than another object (`obj1 > obj2`) or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to say the truth, such comparisons occur very rarely in real code and usually are a result of a coding mistake.
|
||||
For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to say the truth, such comparisons occur very rarely in real code and usually are a result of a coding mistake.
|
||||
|
||||
## Cloning and merging, Object.assign
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
Try running it:
|
||||
|
||||
```js run
|
||||
let str = "Hello";
|
||||
|
||||
str.test = 5; // (*)
|
||||
|
||||
alert(str.test);
|
||||
```
|
||||
|
||||
There may be two kinds of result:
|
||||
1. `undefined`
|
||||
2. An error.
|
||||
|
||||
Why? Let's replay what's happening at line `(*)`:
|
||||
|
||||
1. When a property of `str` is accessed, a "wrapper object" is created.
|
||||
2. The operation with the property is carried out on it. So, the object gets the `test` property.
|
||||
3. The operation finishes and the "wrapper object" disappears.
|
||||
|
||||
So, on the last line, `str` has no trace of the property. A new wrapper object for every object operation on a string.
|
||||
|
||||
Some browsers though may decide to further limit the programmer and disallow to assign properties to primitives at all. That's why in practice we can also see errors at line `(*)`. It's a little bit farther from the specification though.
|
||||
|
||||
**This example clearly shows that primitives are not objects.**
|
||||
|
||||
They just can not store data.
|
||||
|
||||
All property/method operations are performed with the help of temporary objects.
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Can I add a string property?
|
||||
|
||||
|
||||
Consider the following code:
|
||||
|
||||
```js
|
||||
let str = "Hello";
|
||||
|
||||
str.test = 5;
|
||||
|
||||
alert(str.test);
|
||||
```
|
||||
|
||||
How do you think, will it work? What will be shown?
|
|
@ -214,7 +214,6 @@ In this case `this` is `undefined` in strict mode. If we try to access `this.nam
|
|||
|
||||
In non-strict mode (if you forgot `use strict`) the value of `this` in such case will be the *global object* (`"window"` for browser, we'll study it later). This is just a historical thing that `"use strict"` fixes.
|
||||
|
||||
|
||||
Please note that usually a call of a function using `this` without an object is not normal, but rather a programming mistake. If a function has `this`, then it is usually meant to be called in the context of an object.
|
||||
|
||||
```smart header="The consequences of unbound `this`"
|
||||
|
@ -224,7 +223,6 @@ The idea of unbound, run-time evaluated `this` has both pluses and minuses. From
|
|||
|
||||
Here we are not to judge whether this language design decision is good or bad. We will understand how to work with it, how to get benefits and evade problems.
|
||||
```
|
||||
|
||||
## Internals: Reference Type
|
||||
|
||||
An intricate method call can loose `this`, for instance:
|
||||
|
|
|
@ -85,6 +85,39 @@ let user = new function() {
|
|||
The constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code for a single complex object only.
|
||||
````
|
||||
|
||||
## Dual-use constructors: new.target
|
||||
|
||||
Inside a function, we can check how it is called with `new` or without it, using a special `new.target` property.
|
||||
|
||||
It is empty for ordinary runs and equals the function if called with `new`:
|
||||
|
||||
```js run
|
||||
function User() {
|
||||
alert(new.target);
|
||||
}
|
||||
|
||||
User(); // undefined
|
||||
new User(); // function User { ... }
|
||||
```
|
||||
|
||||
That can be used to allow both `new` and ordinary syntax work the same:
|
||||
|
||||
|
||||
```js run
|
||||
function User(name) {
|
||||
if (!new.target) { // if you run me without new
|
||||
return new User(name); // ...I will add new for you
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
let john = User("John"); // redirects call to new User
|
||||
alert(john.name); // John
|
||||
```
|
||||
|
||||
This approach is sometimes used in libraries to make the syntax more flexible. Probably not a good thing to use everywhere though, because it makes a bit less obvious what's going on for a person who's familiar with internals of `User`.
|
||||
|
||||
## Return from constructors
|
||||
|
||||
Usually, constructors do not have a `return` statement. Their task is to write all necessary stuff into `this`, and it automatically becomes the result.
|
||||
|
|
|
@ -16,15 +16,15 @@ The conversion of an object to primitive value (a number or a string) is a rare
|
|||
|
||||
Just think about cases when such conversion may be necessary. For instance, numeric conversion happens when we compare an object against a primitive: `user > 18`. But what such comparison actually means? Are we going to compare `18` against user's age? Then it would be more obvious to write `user.age > 18`. And it's easier to read and understand it too.
|
||||
|
||||
Or, for a string conversion... Where does it happen? Usually, when we output an object. But simple ways of object-as-string output like `alert(user)` are only used for debugging and logging purposes. For real stuff, the output is more complicated, we may need to provide it additional parameters. That's why we usually implement it using object methods like `user.format()` or even in more advanced ways.
|
||||
Or, for a string conversion... Where does it happen? Usually, when we output an object. But simple ways of object-as-string output like `alert(user)` are only used for debugging and logging purposes. For real stuff, the output is more complicated, we may need to configure it with additional parameters. That's why it is usually implemented with object methods like `user.format()` or even in more advanced ways.
|
||||
|
||||
So, most of the time, it's more flexible and gives more readable code to explicitly write an object property or call a method than rely on the conversion.
|
||||
|
||||
That said, there are still valid reasons why we should know how to-primitive conversion works.
|
||||
|
||||
- Simple object-as-string output may be useable sometimes. Without a customized conversion it will show `[object Object]`.
|
||||
- Simple object-as-string output may be useable sometimes.
|
||||
- Many built-in objects implement their own to-primitive conversion, we plan to cover that.
|
||||
- Sometimes it just happens (on mistake?), and we should understand what's going on.
|
||||
- Sometimes an unexpected conversion happens, and we should understand what's going on.
|
||||
- Okay, the final one. There are quizzes and questions on interviews that rely on that knowledge. Looks like people think it's a good sigh that person understands Javascript if he knows type conversions well.
|
||||
|
||||
## ToPrimitive
|
||||
|
@ -78,7 +78,7 @@ There are 3 types (also called "hints") of object-to-primitive conversion:
|
|||
|
||||
Please note -- there are only three conversions. That simple. There is no "boolean" (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions.
|
||||
|
||||
To do the conversion, Javascript tries to find and call these three object methods:
|
||||
To do the conversion, Javascript tries to find and call three object methods:
|
||||
|
||||
1. `Symbol.toPrimitive(hint)` if exists,
|
||||
2. Otherwise if hint is `"string"`, try `toString()` and `valueOf()`, whatever exists.
|
||||
|
@ -109,7 +109,7 @@ As we can see from the code, `user` becomes a self-descriptive string or a money
|
|||
|
||||
### toString/valueOf
|
||||
|
||||
Methods `toString` and `valueOf` come from the ancient times. That's why they are not symbols. They provide an alternative "old-style" way to implement the conversion.
|
||||
Methods `toString` and `valueOf` come from ancient times. That's why they are not symbols. They provide an alternative "old-style" way to implement the conversion.
|
||||
|
||||
If there's no `Symbol.toPrimitive` then Javascript tries to find them and try in the order:
|
||||
|
||||
|
@ -166,20 +166,20 @@ There is no control whether `toString()` returns exactly a string, or whether `S
|
|||
|
||||
**The only mandatory thing: these methods must return a primitive.**
|
||||
|
||||
An operation that was the reason for the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary.
|
||||
An operation that initiated the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary.
|
||||
|
||||
For instance:
|
||||
|
||||
- All mathematical operations except binary plus apply `ToNumber`:
|
||||
- All mathematical operations except binary plus apply `ToNumber` after `ToPrimitive` with `"number"` hint:
|
||||
|
||||
```js run
|
||||
let obj = {
|
||||
toString() { // toString used for numeric conversion in the absense of valueOf
|
||||
toString() { // toString handles all ToPrimitive in the absense of other methods
|
||||
return "2";
|
||||
}
|
||||
};
|
||||
|
||||
alert(obj * 2); // 4
|
||||
alert(obj * 2); // 4, ToPrimitive gives "2", then it becomes 2
|
||||
```
|
||||
|
||||
- Binary plus first checks if the primitive is a string, and then does concatenation, otherwise performs `ToNumber` and works with numbers.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue