merges
This commit is contained in:
parent
779601901f
commit
5372c18379
152 changed files with 482 additions and 371 deletions
|
@ -1,5 +1,5 @@
|
|||
|
||||
# Objects
|
||||
# Objects
|
||||
|
||||
As we know, there are 7 language types in Javascript. Six of them are called "primitive", because their values contain only a single thing (be it a string or a number or whatever).
|
||||
|
||||
|
@ -7,7 +7,7 @@ In contrast, objects are used to store keyed collections of various data and mor
|
|||
|
||||
[cut]
|
||||
|
||||
An object can be created with figure brackets `{…}` with an optional list of "key: value" pairs. In programming that's sometimes called an "associative array" or a "hash".
|
||||
An object can be created with figure brackets `{…}` with an optional list of *properties*. A property is a "key: value" pair, where `key` is a string (also called a "property name"), and `value` can be anything.
|
||||
|
||||
We can imagine an object as a cabinet with signed files. Every piece of data is stored in it's file by the key. It's easy to find a file by it's name or add/remove a file.
|
||||
|
||||
|
@ -26,7 +26,7 @@ Usually, the figure brackets `{...}` are used. That declaration is called an *ob
|
|||
|
||||
## Literals and properties
|
||||
|
||||
We can immediately put some data into `{...}` as "key: value" pairs. Every pair is called *an object property*:
|
||||
We can immediately put some properties into `{...}` as "key: value" pairs:
|
||||
|
||||
```js
|
||||
let user = { // an object
|
||||
|
@ -35,15 +35,15 @@ let user = { // an object
|
|||
};
|
||||
```
|
||||
|
||||
A property has an identifier (also "name" and "key") before the colon `":"` and a value to the right of it.
|
||||
A property has a key (also known as "name" and "identifier") before the colon `":"` and a value to the right of it.
|
||||
|
||||
In the `user` object, there are two properties:
|
||||
|
||||
1. The first property has the name `"name"` and the value `"John"`.
|
||||
1. The first property has the name `"name"` and the value `"John"`.
|
||||
2. The second one has the name `"age"` and the value `30`.
|
||||
|
||||
The resulting `user` object can be imagined as a cabinet with two signed files labelled "name" and "age".
|
||||
|
||||
|
||||

|
||||
|
||||
We can add, remove and read files from it any time.
|
||||
|
@ -56,15 +56,15 @@ alert( user.name ); // John
|
|||
alert( user.age ); // 30
|
||||
```
|
||||
|
||||
A value can be of any time, let's add a boolean one:
|
||||
The value can be of any type. Let's add a boolean one:
|
||||
|
||||
```js
|
||||
user.isAdmin = true;
|
||||
user.isAdmin = true;
|
||||
```
|
||||
|
||||

|
||||
|
||||
...And remove `age` with the help of `delete` operator:
|
||||
To remove a property, we can use `delete` operator:
|
||||
|
||||
```js
|
||||
delete user.age;
|
||||
|
@ -74,47 +74,73 @@ delete user.age;
|
|||
|
||||
We can also use multiword property names, but then they must be quoted:
|
||||
|
||||
```js
|
||||
```js
|
||||
let user = {
|
||||
name: "John",
|
||||
age: 30,
|
||||
"likes birds": true // multiword property name must be quoted
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
````smart header="Trailing comma"
|
||||
The last property in the list may end with a comma:
|
||||
```js
|
||||
let user = {
|
||||
name: "John",
|
||||
age: 30*!*,*/!*
|
||||
}
|
||||
```
|
||||
That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike.
|
||||
````
|
||||
|
||||
## Square brackets
|
||||
|
||||
For multiword properties, the dot access won't work:
|
||||
|
||||
```js
|
||||
```js run
|
||||
// this would give a syntax error
|
||||
user.likes birds = true
|
||||
```
|
||||
|
||||
That's because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations.
|
||||
That's because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations.
|
||||
|
||||
There's an alternative "square bracket notation" that works with any string:
|
||||
|
||||
```js
|
||||
|
||||
```js run
|
||||
let user = {};
|
||||
|
||||
// set
|
||||
user["likes birds"] = true;
|
||||
|
||||
// get
|
||||
alert(user["likes birds"]); // true
|
||||
|
||||
// delete
|
||||
delete user["likes birds"];
|
||||
```
|
||||
|
||||
Square brackets are also the way to access a property by the name from the variable:
|
||||
Now everything is fine. Please note that the string inside the brackets is properly quoted (any type of quotes will do).
|
||||
|
||||
Square brackets are also provide a way to access a property by the name from the variable:
|
||||
|
||||
```js
|
||||
let key = "likes birds";
|
||||
user[key] = true; // same as above
|
||||
|
||||
// same as user["likes birds"] = true;
|
||||
user[key] = true;
|
||||
```
|
||||
|
||||
The square brackets mean: "take the property name from the variable".
|
||||
Here, the variable `key` may be calculated at run-time or depend on the user input. And then we use it to access the property. That gives us a great deal of flexibility. The dot notation cannot be used in similar way.
|
||||
|
||||
That variable can be assigned at run-time, for instance:
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
name: "John",
|
||||
age: 30
|
||||
};
|
||||
|
||||
|
@ -124,9 +150,12 @@ let key = prompt("What do you want to know about the user?", "name");
|
|||
alert( user[key] ); // John (if enter "name")
|
||||
```
|
||||
|
||||
|
||||
### Computed properties
|
||||
|
||||
*Computed properties* are square brackets used inside an object literal:
|
||||
We can use square brackets in an object literal. That's called *computed properties*.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let fruit = prompt("Which fruit to buy?", "apple");
|
||||
|
@ -140,7 +169,9 @@ let bag = {
|
|||
alert( bag.apple ); // 5 if fruit="apple"
|
||||
```
|
||||
|
||||
Here, the value of `fruit` variable is used as the property name. So, if a visitor enters `"apple"`, `bag` will become `{apple: 5}`.
|
||||
The meaning of a computed property is simple: `[fruit]` means that the property name should be taken from `fruit`.
|
||||
|
||||
So, if a visitor enters `"apple"`, `bag` will become `{apple: 5}`.
|
||||
|
||||
Essentially, that works the same as:
|
||||
```js run
|
||||
|
@ -148,10 +179,12 @@ let fruit = prompt("Which fruit to buy?", "apple");
|
|||
let bag = {};
|
||||
|
||||
// take property name from the fruit variable
|
||||
bag[fruit] = 5;
|
||||
bag[fruit] = 5;
|
||||
```
|
||||
|
||||
We can use more complex expressions inside square brackets. Anything that results in a property name:
|
||||
...But looks nicer.
|
||||
|
||||
We can use more complex expressions inside square brackets:
|
||||
|
||||
```js
|
||||
let fruit = 'apple';
|
||||
|
@ -160,26 +193,16 @@ let bag = {
|
|||
};
|
||||
```
|
||||
|
||||
Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are more cumbersome to write. So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.
|
||||
Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are also more cumbersome to write.
|
||||
|
||||
So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.
|
||||
|
||||
|
||||
````smart header="Trailing comma"
|
||||
The last property in the list may end with a comma:
|
||||
```js
|
||||
let user = {
|
||||
name: "John",
|
||||
age: 30*!*,*/!*
|
||||
}
|
||||
```
|
||||
That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike.
|
||||
````
|
||||
|
||||
|
||||
````smart header="Reserved words are allowed as property names"
|
||||
A variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc.
|
||||
|
||||
But for an object property, there's no such restruction. Any name is fine:
|
||||
But for an object property, there's no such restriction. Any name is fine:
|
||||
|
||||
```js run
|
||||
let obj = {
|
||||
|
@ -194,11 +217,12 @@ alert( obj.for + obj.let + obj.return ); // 6
|
|||
Basically, any name is allowed, but there's a special one: `"__proto__"` that gets special treatment for historical reasons. For instance, we can't set it to a non-object value:
|
||||
|
||||
```js run
|
||||
let obj = { __proto__: 5 };
|
||||
let obj = {};
|
||||
obj.__proto__ = 5;
|
||||
alert(obj.__proto__); // [object Object], didn't work as intended
|
||||
```
|
||||
|
||||
Later we'll learn more about that `__proto__` and see how to work that problem [todo Object.create(null)]. Also we'll learn another data structure [Map](info:map-set-weakmap-weakset) that doesn't have such problems and supports arbitrary keys.
|
||||
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.
|
||||
````
|
||||
|
||||
|
||||
|
@ -209,11 +233,10 @@ In real code we often use existing variables as values for property names.
|
|||
For instance:
|
||||
|
||||
```js run
|
||||
function makeUser(name, age) {
|
||||
*!*
|
||||
// take values for name and age from variables
|
||||
*/!*
|
||||
return {
|
||||
function makeUser() {
|
||||
let name = prompt("Name?");
|
||||
let age = prompt("Age?");
|
||||
return {
|
||||
name: name,
|
||||
age: age
|
||||
};
|
||||
|
@ -223,7 +246,7 @@ let user = makeUser("John", 30);
|
|||
alert(user.name); // John
|
||||
```
|
||||
|
||||
In the example above, properties have same names as variables. There's a special shorthand notation that is shorter.
|
||||
In the example above, properties have same names as variables. The use-case of making a property from a variable is so common, that there's a special *property value shorthand* to make it shorter.
|
||||
|
||||
Instead of `name:name` we can just write `name`, like this:
|
||||
|
||||
|
@ -241,9 +264,9 @@ function makeUser(name, age) {
|
|||
We can use both normal properties and shorthands in the same object:
|
||||
|
||||
```js
|
||||
let user = {
|
||||
let user = {
|
||||
name, // same as name:name
|
||||
age: 30
|
||||
age: 30
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -258,9 +281,9 @@ let user = {};
|
|||
alert( user.noSuchProperty === undefined ); // true means "no such property"
|
||||
```
|
||||
|
||||
There also exists a special operator `"in"` to check for the existance of a property.
|
||||
There also exists a special operator `"in"` to check for the existance of a property.
|
||||
|
||||
The syntax is:
|
||||
The syntax is:
|
||||
```js
|
||||
"key" in object
|
||||
```
|
||||
|
@ -274,7 +297,7 @@ alert( "age" in user ); // true, user.age exists
|
|||
alert( "blabla" in user ); // false, user.blabla doesn't exist
|
||||
```
|
||||
|
||||
Please note that at the left side of `in` there must be a *property name*. That's usually a quoted string.
|
||||
Please note that at the left side of `in` there must be a *property name*. That's usually a quoted string.
|
||||
|
||||
If we omit quotes, that would mean a variable containing the actual name to be tested. For instance:
|
||||
|
||||
|
@ -291,9 +314,9 @@ Usually, the strict comparison `"=== undefined"` check works fine. But there's a
|
|||
It's when an object property exists, but stores `undefined`:
|
||||
|
||||
```js run
|
||||
let obj = {
|
||||
test: undefined
|
||||
};
|
||||
let obj = {
|
||||
test: undefined
|
||||
};
|
||||
|
||||
alert( obj.test ); // it's undefined, so - no such property?
|
||||
|
||||
|
@ -309,7 +332,7 @@ Situations like this happen very rarely, because `undefined` is usually not assi
|
|||
|
||||
## The "for..in" loop
|
||||
|
||||
To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before.
|
||||
To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before.
|
||||
|
||||
The syntax:
|
||||
|
||||
|
@ -336,16 +359,16 @@ for(let key in user) {
|
|||
}
|
||||
```
|
||||
|
||||
Note that all "for" constructs allow to declare the looping variable inside the loop, like `let key` here.
|
||||
Note that all "for" constructs allow to declare the looping variable inside the loop, like `let key` here.
|
||||
|
||||
Also, we could use another variable name here instead of `key`. For instance, `"for(let prop in obj)"` is also widely used.
|
||||
|
||||
|
||||
### Ordered like an object
|
||||
|
||||
Are objects ordered? In other words, if we loop over an object, do we get all properties in the same order that they are added in it?
|
||||
Are objects ordered? In other words, if we loop over an object, do we get all properties in the same order that they are added in it? Can we rely on it?
|
||||
|
||||
The short answer is: "ordered like an object": integer properties are sorted, others appear in creation order. The details follow.
|
||||
The short answer is: "ordered in a special fashion": integer properties are sorted, others appear in creation order. The details follow.
|
||||
|
||||
As an example, let's consider an object with the phone codes:
|
||||
|
||||
|
@ -367,7 +390,7 @@ for(let code in codes) {
|
|||
|
||||
The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want `49` to be the first.
|
||||
|
||||
But if we run the code, we see a totally different picture:
|
||||
But if we run the code, we see a totally different picture:
|
||||
|
||||
- USA (1) goes first
|
||||
- then Switzerland (41) and so on.
|
||||
|
@ -375,11 +398,12 @@ But if we run the code, we see a totally different picture:
|
|||
The phone codes go in the ascending sorted order, because they are integer. So we see `1, 41, 44, 49`.
|
||||
|
||||
````smart header="Integer properties? What's that?"
|
||||
The "integer property" term here means a string that can be converted to-from integer without a change.
|
||||
The "integer property" term here means a string that can be converted to-and-from integer without a change.
|
||||
|
||||
So, "49" is an integer property name, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not:
|
||||
|
||||
```js run
|
||||
// Math.trunc is a built-in function that removes the decimal part
|
||||
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
|
||||
alert( String(Math.trunc(Number("+49"))) ); // "49", not same ⇒ not integer property
|
||||
alert( String(Math.trunc(Number("1.2"))) ); // "1", not same ⇒ not integer property
|
||||
|
@ -417,15 +441,13 @@ let codes = {
|
|||
};
|
||||
|
||||
for(let code in codes) {
|
||||
alert( +code ); // 49, 41, 44, 1
|
||||
alert( +code ); // 49, 41, 44, 1
|
||||
}
|
||||
```
|
||||
|
||||
Now it works as intended.
|
||||
Now it works as intended.
|
||||
|
||||
|
||||
|
||||
## References
|
||||
## Copying by reference
|
||||
|
||||
One of fundamental differences of objects vs primitives is that they are stored and copied "by reference".
|
||||
|
||||
|
@ -456,7 +478,7 @@ let user = {
|
|||
|
||||

|
||||
|
||||
Note that the object itself is stored somewhere in memory. The variable `user` has a "reference" to it.
|
||||
Here, the object is stored somewhere in memory. And the variable `user` has a "reference" to it.
|
||||
|
||||
**When an object variable is copied -- the reference is copied, the object is not duplicated.**
|
||||
|
||||
|
@ -465,7 +487,7 @@ If we imagine an object as a cabinet, then a variable is a key to it. Copying a
|
|||
For instance:
|
||||
|
||||
```js no-beautify
|
||||
let user = { name: "John" };
|
||||
let user = { name: "John" };
|
||||
|
||||
let admin = user; // copy the reference
|
||||
```
|
||||
|
@ -488,7 +510,7 @@ admin.name = 'Pete'; // changed by the "admin" reference
|
|||
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.
|
||||
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. Then, if we later use the other key (`user`) we see changes.
|
||||
|
||||
### Comparison by reference
|
||||
|
||||
|
@ -515,7 +537,7 @@ let b = {}; // two independent objects
|
|||
alert( a == b ); // false
|
||||
```
|
||||
|
||||
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.
|
||||
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 are necessary very rarely and usually are a result of a coding mistake.
|
||||
|
||||
## Cloning and merging, Object.assign
|
||||
|
||||
|
@ -547,7 +569,7 @@ for (let key in user) {
|
|||
// now clone is a fully independant clone
|
||||
clone.name = "Pete"; // changed the data in it
|
||||
|
||||
alert( user.name ); // still John
|
||||
alert( user.name ); // still John in the original object
|
||||
```
|
||||
|
||||
Also we can use the method [Object.assign](mdn:js/Object/assign) for that.
|
||||
|
@ -558,12 +580,11 @@ The syntax is:
|
|||
Object.assign(dest[, src1, src2, src3...])
|
||||
```
|
||||
|
||||
- `dest` and other arguments (can be as many as needed) are objects
|
||||
|
||||
It copies the properties of all arguments starting from the 2nd (`src1`, `src2` etc) into the `dest`. Then it returns `dest`.
|
||||
- Arguments `dest`, and `src1, ..., srcN` (can be as many as needed) are objects.
|
||||
- It copies the properties of all objects `src1, ..., srcN` into `dest`. In other words, properties of all arguments starting from the 2nd are copied into the 1st. Then it returns `dest`.
|
||||
|
||||
For instance, we can use it to merge several objects into one:
|
||||
```js
|
||||
```js
|
||||
let user = { name: "John" };
|
||||
|
||||
let permissions1 = { canView: true };
|
||||
|
@ -579,7 +600,7 @@ Object.assign(user, permissions1, permissions2);
|
|||
|
||||
If the receiving object (`user`) already has the same named property, it will be overwritten:
|
||||
|
||||
```js
|
||||
```js
|
||||
let user = { name: "John" };
|
||||
|
||||
// overwrite name, add isAdmin
|
||||
|
@ -590,14 +611,14 @@ Object.assign(user, { name: "Pete", isAdmin: true });
|
|||
|
||||
We also can use `Object.assign` to replace the loop for simple cloning:
|
||||
|
||||
```js
|
||||
```js
|
||||
let user = {
|
||||
name: "John",
|
||||
age: 30
|
||||
};
|
||||
|
||||
*!*
|
||||
let clone = Object.assign({}, user);
|
||||
let clone = Object.assign({}, user);
|
||||
*/!*
|
||||
```
|
||||
|
||||
|
@ -634,11 +655,12 @@ let clone = Object.assign({}, user);
|
|||
|
||||
alert( user.sizes === clone.sizes ); // true, same object
|
||||
|
||||
// user and clone share sizes
|
||||
user.sizes.width++; // change a property from one place
|
||||
alert(clone.sizes.width); // 51, see the result from the other one
|
||||
```
|
||||
|
||||
To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate it's structure as well. That is called a "deep cloning".
|
||||
To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate it's structure as well. That is called a "deep cloning".
|
||||
|
||||
There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](w3c.github.io/html/infrastructure.html#internal-structured-cloning-algorithm). Not to reinvent the wheel, we can use a working implementation of it from the Javascript library [lodash](https://lodash.com), the method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
|
||||
|
||||
|
@ -667,9 +689,9 @@ Property access:
|
|||
- Integer properties in sorted order first, then strings in creation order, then symbols in creation order.
|
||||
- To keep the order for numeric properties, we can prepend them with `+` to make them look like non-numeric.
|
||||
|
||||
- Objects are assigned and copied by reference.
|
||||
- Objects are assigned and copied by reference.
|
||||
|
||||
What we've just seen is called a "plain object", or just `Object`.
|
||||
What we've just seen is called a "plain object", or just `Object`.
|
||||
|
||||
There are many other kinds of objects in Javascript:
|
||||
|
||||
|
@ -681,4 +703,3 @@ There are many other kinds of objects in Javascript:
|
|||
Sometimes people say something like "Array type" or "Date type", but formally they are not types of their own, but belong to a single "object" data type. And they extend it in various ways.
|
||||
|
||||
Objects in JavaScript are very powerful. Here we've just scratched the surface of the topic that is really huge. We'll be closely working with objects and learning more about them in further parts of the tutorial.
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ What happens when something is not needed any more? How Javascript engine discov
|
|||
|
||||
## Reachability
|
||||
|
||||
The main concept of memory management in Javascript is *reachability*.
|
||||
The main concept of memory management in Javascript is *reachability*.
|
||||
|
||||
Simply put, "reachable" values are those that are accessible now or in the future. They are guaranteed to be stored in memory.
|
||||
Simply put, "reachable" values are those that are accessible or useable somehow. They are guaranteed to be stored in memory.
|
||||
|
||||
1. There's a base set of inherently reachable values, that cannot be deleted for obvious reasons.
|
||||
1. There's a base set of inherently reachable values, that cannot be deleted for obvious reasons.
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -21,11 +21,13 @@ Simply put, "reachable" values are those that are accessible now or in the futur
|
|||
- Global variables.
|
||||
- (there are some other, internal ones as well)
|
||||
|
||||
These values are called *roots*.
|
||||
These values are called *roots*.
|
||||
|
||||
2. Any other value is retained in memory only while it's reachable from a root by a reference of by a chain of references.
|
||||
2. Any other value is considered reachable if it's reachable from a root by a reference of by a chain of references.
|
||||
|
||||
There's a background process that runs by the engine itself called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that became unreachable.
|
||||
For instance, if there's an object in a local variable, and that object has a property referencing another object, that object is considered reachable. And those that it references -- are also reachable. Detailed examples to follow.
|
||||
|
||||
There's a background process in the Javascript engine that is called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that became unreachable.
|
||||
|
||||
## A simple example
|
||||
|
||||
|
@ -70,7 +72,7 @@ let admin = user;
|
|||

|
||||
|
||||
Now if we do the same:
|
||||
```js
|
||||
```js
|
||||
user = null;
|
||||
```
|
||||
|
||||
|
@ -104,7 +106,7 @@ The resulting memory structure:
|
|||
|
||||

|
||||
|
||||
As of now, all objects are reachable.
|
||||
As of now, all objects are reachable.
|
||||
|
||||
Now let's remove two references:
|
||||
|
||||
|
@ -143,7 +145,7 @@ The in-memory picture becomes:
|
|||
|
||||
This example demonstrates how important the concept of reachability is.
|
||||
|
||||
It's obvious that John and Ann are still linked, both have incoming references. But that's not enough.
|
||||
It's obvious that John and Ann are still linked, both have incoming references. But that's not enough.
|
||||
|
||||
The former `"family"` object has been unlinked from the root, there's no reference to it any more, so the whole island becomes unreachable and will be removed.
|
||||
|
||||
|
@ -153,19 +155,19 @@ The basic garbage collection algorithm is called "mark-and-sweep".
|
|||
|
||||
Regularly the following "garbage collection" steps are performed:
|
||||
|
||||
- The garbage collector takes roots and "marks" them.
|
||||
- The garbage collector takes roots and "marks" (remembers) them.
|
||||
- Then it visits and "marks" all references from them.
|
||||
- Then it visits marked objects and marks *their* references. All visited objects are remembered, not to visit the same object twice in the future.
|
||||
|
||||
- ...And so on until there are unvisited references (reachable from the roots).
|
||||
- All objects except marked ones are removed.
|
||||
|
||||
|
||||
For instance, if our object structure looks like this:
|
||||
For instance, let our object structure look like this:
|
||||
|
||||

|
||||
|
||||
Then the first step marks the roots:
|
||||
We can clearly see an "unreachable island" to the right side. Now let's see how "mark-and-sweep" garbage collector deals with it.
|
||||
|
||||
The first step marks the roots:
|
||||
|
||||

|
||||
|
||||
|
@ -183,15 +185,17 @@ Now the objects that could not be visited in the process are considered unreacha
|
|||
|
||||
That's the concept how garbage collection works.
|
||||
|
||||
Javascript engines apply many optimizations to it, to make it run faster and be more hidden behind the scenes.
|
||||
Javascript engines apply many optimizations to make it run faster and not affect the execution.
|
||||
|
||||
Some of the optimizations:
|
||||
|
||||
- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, so they can be cleaned up more aggressively. Those "new" that survive for long enough, become "old".
|
||||
- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays. So the engine tries to split the job into pieces. Then pieces are executed one at a time. That requires some extra bookkeeping between them.
|
||||
- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, then do their job and die fast, so they can be cleaned up aggressively. Those that survive for long enough, become "old".
|
||||
- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine tries to split the job into pieces. Then pieces are executed one at a time. That requires some extra bookkeeping between them to track changes.
|
||||
- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution.
|
||||
|
||||
Detailed learning of these optimization is also possible, but it requires a lot of under-the-hood digging. Javascript engines implement garbage collection differently. And -- what's even more important, things change, so going really deep "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below.
|
||||
There are other optimizations and flavours of garbage collection algorithms. As much as I'd like to describe them here, I have to hold off, because different engines implement different tweaks and techniques.
|
||||
|
||||
And -- what's even more important, things change as engines develop, so going really deep "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below.
|
||||
|
||||
## Summary
|
||||
|
||||
|
@ -203,8 +207,10 @@ The main things to know:
|
|||
|
||||
Modern engines implement advanced algorithms of garbage collection.
|
||||
|
||||
If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection), and [V8 blog](http://v8project.blogspot.com/) also publishes articles about changes in memory management from time to time. Also you'd better prepare yourself by learning about V8 in general and read the blog of [Vyacheslav Egorov](http://mrale.ph) who worked as one of V8 engineers. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects.
|
||||
A general book "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones at al) covers some of them.
|
||||
|
||||
If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection).
|
||||
|
||||
[V8 blog](http://v8project.blogspot.com/) also publishes articles about changes in memory management from time to time. Naturally, to learn the garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](http://mrale.ph) who worked as one of V8 engineers. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects.
|
||||
|
||||
In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language.
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
let ladder = {
|
||||
step: 0,
|
||||
up: function() {
|
||||
this.step++;
|
||||
return this;
|
||||
},
|
||||
down: function() {
|
||||
this.step--;
|
||||
return this;
|
||||
},
|
||||
showStep: function() {
|
||||
alert(this.step);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
describe('Ladder', function() {
|
||||
before(function() {
|
||||
window.alert = sinon.stub(window, "alert");
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
ladder.step = 0;
|
||||
});
|
||||
|
||||
it('up() should return this', function() {
|
||||
assert.equal(ladder.up(), ladder);
|
||||
});
|
||||
|
||||
it('down() should return this', function() {
|
||||
assert.equal(ladder.down(), ladder);
|
||||
});
|
||||
|
||||
it('showStep() should call alert', function() {
|
||||
ladder.showStep();
|
||||
assert(alert.called);
|
||||
});
|
||||
|
||||
it('up() should increase step', function() {
|
||||
assert.equal(ladder.up().up().step, 2);
|
||||
});
|
||||
|
||||
it('down() should decrease step', function() {
|
||||
assert.equal(ladder.down().step, -1);
|
||||
});
|
||||
|
||||
it('down().up().up().up() ', function() {
|
||||
assert.equal(ladder.down().up().up().up().step, 2);
|
||||
});
|
||||
|
||||
after(function() {
|
||||
ladder.step = 0;
|
||||
alert.restore();
|
||||
});
|
||||
});
|
|
@ -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?
|
|
@ -1,95 +0,0 @@
|
|||
# Methods of primitives
|
||||
|
||||
JavaScript allows to work with primitives (strings, numbers etc) as if they were objects. They also have methods and such. Of course, primitives are not objects (and here we plan to make it even more clear), but can be used like them.
|
||||
|
||||
[cut]
|
||||
|
||||
Let's formulate the key distinction between primitives and objects.
|
||||
|
||||
A primitive
|
||||
: Is a value of a primitive type. There are 6 primitive types: `string`, `number`, `boolean`, `symbol`, `null` and `undefined`.
|
||||
|
||||
An object
|
||||
: Is capable of storing multiple values as properties.
|
||||
Can be created with `{}`, for instance: `{name: "John", age: 30}`. There are other kinds of objects in JavaScript, e.g. functions are objects.
|
||||
|
||||
One of the best thing about objects is that we can store a function as one of properties:
|
||||
|
||||
```js run
|
||||
let john = {
|
||||
name: "John",
|
||||
sayHi: function() {
|
||||
alert("Hi buddy!");
|
||||
}
|
||||
};
|
||||
|
||||
john.sayHi(); // Hi buddy!
|
||||
```
|
||||
|
||||
So, here we've made an object `john` with the method `sayHi`.
|
||||
|
||||
There exist many built-in objects, including those that work with dates, errors, HTML elements etc. They have different properties and methods.
|
||||
|
||||
But features come at a price!
|
||||
|
||||
Objects are "heavier" than primitives. They require additional resources to support the internal machinery. But properties and methods are useful in programming, Javascript engines try to optimize them, so the price is usually fair.
|
||||
|
||||
## A primitive as an object
|
||||
|
||||
Here's the paradox faced by the creator of JavaScript:
|
||||
|
||||
- There are many things one would want to do with a primitive like a string or a number. Could be great to access them as methods.
|
||||
- Primitives must be as fast and lightweight as possible.
|
||||
|
||||
The solution looks a little bit awkward, but here it is.
|
||||
|
||||
1. Primitives are still primitive. A single value, as desired.
|
||||
2. The language allows to access methods and properties of strings, numbers, booleans and symbols.
|
||||
3. When it happens, a special "object wrapper" is created that provides the functionality and then is destroyed.
|
||||
|
||||
The "object wrappers" are different for each primitive type and are named specifically: `String`, `Number`, `Boolean` and `Symbol`. Thus they provide different sets of methods.
|
||||
|
||||
For instance, there exists a method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns the capitalized string.
|
||||
|
||||
Here's how it works:
|
||||
|
||||
```js run
|
||||
let str = "Hello";
|
||||
|
||||
alert( str.toUpperCase() ); // HELLO
|
||||
```
|
||||
|
||||
Simple, right? And here's what actually happens in `str.toUpperCase()`:
|
||||
|
||||
1. The string `str` is a primitive. So in the moment of accessing its property a special object is created that both knows the value of the string and has useful methods, like `toUpperCase()`.
|
||||
2. That method runs and returns a new string (shown by `alert`).
|
||||
3. The special object is destroyed, leaving the primitive `str` alone.
|
||||
|
||||
So, primitives can provide methods, but they still remain lightweight.
|
||||
|
||||
Of course, a JavaScript engine highly optimizes that process. Internally it may skip the creation of the extra object at all. But it must adhere to the specification and behave as if it creates one.
|
||||
|
||||
A number has methods of it's own, for instance, [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rounds the number to the given precision:
|
||||
|
||||
```js run
|
||||
let n = 1.23456;
|
||||
|
||||
alert( n.toFixed(2) ); // 1.23
|
||||
```
|
||||
|
||||
We'll see more specific methods in chapters <info:number> and <info:string>.
|
||||
|
||||
````warn header="null/undefined have no methods"
|
||||
Special primitives `null` and `undefined` are exceptions. They have no corresponding "wrapper objects" and provide no methods. In a sense, they are "the most primitive".
|
||||
|
||||
An attempt to access a property of such value would give an error:
|
||||
|
||||
```js run
|
||||
alert(null.test); // error
|
||||
````
|
||||
|
||||
## Summary
|
||||
|
||||
- Primitives except `null` and `undefined` provide many helpful methods. We plan to study those in the next chapters.
|
||||
- Formally, these methods work via temporary objects, but JavaScript engines are very well tuned to optimize that internally, so they are not expensive to call.
|
||||
|
|
@ -9,7 +9,7 @@ Till now we only saw strings. Now let's see the advantages that symbols can give
|
|||
|
||||
## Symbols
|
||||
|
||||
"Symbol" value represents an unique identifier with a given name.
|
||||
"Symbol" value represents an unique identifier with a given name.
|
||||
|
||||
A value of this type can be created using `Symbol(name)`:
|
||||
|
||||
|
@ -31,18 +31,18 @@ alert(id1 == id2); // false
|
|||
*/!*
|
||||
```
|
||||
|
||||
If you are familiar with Ruby or another language that also has some sort of "symbols" -- please don't be misguided. Javascript symbols are different.
|
||||
If you are familiar with Ruby or another language that also has some sort of "symbols" -- please don't be misguided. Javascript symbols are different.
|
||||
|
||||
|
||||
## "Private" properties
|
||||
## "Hidden" properties
|
||||
|
||||
Symbols allow to create concealed, "private" properties of an object, that no other part of code can occasionally access or overwrite.
|
||||
Symbols allow 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:
|
||||
|
||||
```js run
|
||||
let user = { name: "John" };
|
||||
let id = Symbol("id");
|
||||
let id = Symbol("id");
|
||||
|
||||
user[id] = "ID Value";
|
||||
alert( user[id] ); // we can access the data using the symbol as the key
|
||||
|
@ -50,11 +50,11 @@ alert( user[id] ); // we can access the data using the symbol as the key
|
|||
|
||||
Now let's imagine that another script wants to have his own "id" property inside `user`, for his own purposes. That may be another Javascript library, so the scripts are completely unaware for each other.
|
||||
|
||||
No problem. It can create its own `Symbol("id")`.
|
||||
No problem. It can create its own `Symbol("id")`.
|
||||
|
||||
Their script:
|
||||
|
||||
```js
|
||||
```js
|
||||
// ...
|
||||
let id = Symbol("id");
|
||||
|
||||
|
@ -77,7 +77,7 @@ user.id = "Their id value"
|
|||
// boom! overwritten! it did not mean to harm the colleague, but did it!
|
||||
```
|
||||
|
||||
### Symbols in literal
|
||||
### Symbols in a literal
|
||||
|
||||
If we want to use a symbol in an object literal, we need square brackets.
|
||||
|
||||
|
@ -93,9 +93,9 @@ let user = {
|
|||
*/!*
|
||||
};
|
||||
```
|
||||
That's because we use the value from the variable `id`, not the string "id".
|
||||
That's because we need the value from the variable `id` as the key, not the string "id".
|
||||
|
||||
### Symbols are not in loop
|
||||
### Symbols skipped by for..in
|
||||
|
||||
Symbolic properties do not participate in `for..in` loop.
|
||||
|
||||
|
@ -114,7 +114,7 @@ for(let key in user) alert(key); // name, age (no symbols)
|
|||
*/!*
|
||||
|
||||
// the direct access by the global symbol works
|
||||
alert( "Direct: " + user[Symbol.for("id")] );
|
||||
alert( "Direct: " + user[Symbol.for("id")] );
|
||||
```
|
||||
|
||||
That's a part of the general "hiding" concept. If another script or a library loops over our object, it won't unexpectedly access a symbolic property.
|
||||
|
@ -132,8 +132,7 @@ let clone = Object.assign({}, user);
|
|||
alert( clone[id] ); // 123
|
||||
```
|
||||
|
||||
There's no paradox here. That's the expected behavior, because when we clone an object, we expect symbolic properties (like `id`) to be copied as well.
|
||||
|
||||
There's no paradox here. That's by design. The idea is that when we clone an object or merge objects, we usually want symbolic properties (like `id`) to be copied as well.
|
||||
|
||||
````smart header="Property keys of other types are coerced to strings"
|
||||
We can only use strings or symbols as keys in objects. Other types are coerced to strings.
|
||||
|
@ -155,7 +154,7 @@ alert( obj[0] ); // test (same property)
|
|||
|
||||
Normally, all symbols are different. But sometimes we want same-named symbols to be the same.
|
||||
|
||||
For instance, different parts of our application want to access symbol `"id"` meaning the 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 and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol.
|
||||
|
||||
|
@ -177,7 +176,7 @@ alert( name === nameAgain ); // 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.
|
||||
|
||||
```smart header="That sounds like Ruby"
|
||||
In some programming languages, like Ruby, there's a single symbol per name.
|
||||
In some programming languages, like Ruby, there's a single symbol per name.
|
||||
|
||||
In Javascript, as we can see, that's right for global symbols.
|
||||
```
|
||||
|
@ -221,7 +220,7 @@ They are listed in the specification in the [Well-known symbols](https://tc39.gi
|
|||
- `Symbol.toPrimitive`
|
||||
- ...and so on.
|
||||
|
||||
For instance, `Symbol.toPrimitive` allows to describe object to primitive conversion. We'll see its use very soon.
|
||||
For instance, `Symbol.toPrimitive` allows to describe object to primitive conversion. We'll see its use very soon.
|
||||
|
||||
Other symbols will also become familiar when we study the corresponding language features.
|
||||
|
||||
|
@ -229,11 +228,11 @@ Other symbols will also become familiar when we study the corresponding language
|
|||
|
||||
- Symbol is a primitive type for unique identifiers.
|
||||
- Symbols are created with `Symbol(name)` call.
|
||||
- Symbols are useful if we want to create a field that only those who know the symbol can access.
|
||||
- Symbols are useful if we want to create a field that only those who know the symbol can access.
|
||||
- Symbols don't appear in `for..in` loops.
|
||||
- Symbols created with `Symbol(name)` are always different, 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 return the same symbol.
|
||||
- There are system symbols used by Javascript and accessible as `Symbol.*`. We can use them to alter some built-in behaviors.
|
||||
|
||||
Technically, symbols are not 100% hidden. There is a build-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows 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 completely hidden and private.
|
||||
Technically, symbols are not 100% hidden. There is a build-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows 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.
|
||||
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.
|
||||
|
|
|
@ -5,7 +5,6 @@ In the chapter <info:type-conversions> we've seen the rules for numeric, string
|
|||
|
||||
But we left a gap for objects. Now let's fill it.
|
||||
|
||||
|
||||
[cut]
|
||||
|
||||
## Where and why?
|
||||
|
@ -16,18 +15,18 @@ 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 configure it with additional parameters. That's why it is usually implemented with 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. 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.
|
||||
So, most of the time, it's more flexible and gives more readable code to explicitly get 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.
|
||||
- Many built-in objects implement their own to-primitive conversion, we plan to cover that.
|
||||
- Simple object-as-string output is useable sometimes.
|
||||
- Many built-in objects implement their own to-primitive conversion, we need to know how to work with that.
|
||||
- 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.
|
||||
- Okay, the final one. There are quizzes and questions on interviews that rely on that knowledge. Looks like people think it's a good sign that person understands Javascript if he knows type conversions well.
|
||||
|
||||
## ToPrimitive
|
||||
## ToPrimitive
|
||||
|
||||
The algorithm of object-to-primitive conversion is called `ToPrimitive` in [the specification](https://tc39.github.io/ecma262/#sec-toprimitive).
|
||||
|
||||
|
@ -39,9 +38,9 @@ There are 3 types (also called "hints") of object-to-primitive conversion:
|
|||
```js
|
||||
// output
|
||||
alert(obj);
|
||||
|
||||
|
||||
// using object as a property key
|
||||
anotherObj[obj] = value;
|
||||
anotherObj[obj] = 123;
|
||||
```
|
||||
|
||||
`"number"`
|
||||
|
@ -52,15 +51,15 @@ There are 3 types (also called "hints") of object-to-primitive conversion:
|
|||
let num = Number(obj);
|
||||
|
||||
// maths (except binary plus)
|
||||
let n = +obj; // unary plus
|
||||
let delta = date1 - date2;
|
||||
let n = +obj; // unary plus
|
||||
let delta = date1 - date2;
|
||||
|
||||
// less/greater comparison
|
||||
let greater = user1 > user2;
|
||||
```
|
||||
|
||||
`"default"`
|
||||
: Occurs in rare cases where it's not clear what is desired.
|
||||
: Occurs in rare cases where it's not clear what is desired.
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -72,11 +71,11 @@ There are 3 types (also called "hints") of object-to-primitive conversion:
|
|||
if (user == 1) { ... };
|
||||
```
|
||||
|
||||
There's some inconsistency here. The greater/less operator `<>` can work with both strings and numbers, it compares them differently. Still, it uses "number" hint. That's for historical reasons.
|
||||
So, binary `+` and `==` use this hint. Seems right, because they operate both with strings and numbers. Although, there's some inconsistency here. The greater/less operator `<>` can work with both strings and numbers too. Still, it uses "number" hint. That's for historical reasons.
|
||||
|
||||
In practice, all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And probably we should do the same.
|
||||
|
||||
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.
|
||||
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 three object methods:
|
||||
|
||||
|
@ -84,7 +83,7 @@ To do the conversion, Javascript tries to find and call three object methods:
|
|||
2. Otherwise if hint is `"string"`, try `toString()` and `valueOf()`, whatever exists.
|
||||
3. Otherwise if hint is `"number"` or `"default"`, try `valueOf()` and `toString()`, whatever exists.
|
||||
|
||||
### Symbol.toPrimitive
|
||||
## Symbol.toPrimitive
|
||||
|
||||
For instance, here `user` object implements the 1st method:
|
||||
|
||||
|
@ -107,9 +106,9 @@ alert(user + 500); // hint: default -> 1500
|
|||
|
||||
As we can see from the code, `user` becomes a self-descriptive string or a money amount depending on the conversion.
|
||||
|
||||
### toString/valueOf
|
||||
## toString/valueOf
|
||||
|
||||
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.
|
||||
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,16 +165,16 @@ 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 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 that primitive, and then continues to work with it, applying further conversions if necessary.
|
||||
|
||||
For instance:
|
||||
|
||||
- All mathematical operations except binary plus apply `ToNumber` after `ToPrimitive` with `"number"` hint:
|
||||
- Mathematical operations (except binary plus) apply `ToNumber` after `ToPrimitive` with `"number"` hint:
|
||||
|
||||
```js run
|
||||
let obj = {
|
||||
toString() { // toString handles all ToPrimitive in the absense of other methods
|
||||
return "2";
|
||||
return "2";
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -187,8 +186,8 @@ For instance:
|
|||
String example:
|
||||
```js run
|
||||
let obj = {
|
||||
toString() {
|
||||
return "2";
|
||||
toString() {
|
||||
return "2";
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -198,8 +197,8 @@ For instance:
|
|||
Number example:
|
||||
```js run
|
||||
let obj = {
|
||||
toString() {
|
||||
return true;
|
||||
toString() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -213,9 +212,6 @@ For instance:
|
|||
|
||||
Minor notes:
|
||||
|
||||
- If `Symbol.toPrimitive` returns an object, that's an error.
|
||||
- If `toString/valueOf` return an object, they are ignored (historical behavior).
|
||||
- If `Symbol.toPrimitive` returns an object, that's an error.
|
||||
- If `toString/valueOf` return an object, they are ignored (historical behavior).
|
||||
- By default, all objects have both `toString` and `valueOf`, but `valueOf` returns the object itself, and hence is ignored.
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue