This commit is contained in:
Ilya Kantor 2016-11-12 19:38:58 +03:00
parent 5372c18379
commit 3defacc09d
314 changed files with 1761 additions and 1704 deletions

View file

@ -226,7 +226,7 @@ As we see from the code, the assignment to a primitive `5` is ignored. If we wan
```` ````
## Property name shorthand ## Property value shorthand
In real code we often use existing variables as values for property names. In real code we often use existing variables as values for property names.
@ -270,7 +270,6 @@ let user = {
}; };
``` ```
## Existance check ## Existance check
A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined: A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined:

View file

@ -9,7 +9,7 @@ let user = {
}; };
``` ```
And, in the real world, a user can `act`: to select something from the shopping cart, to login, to logout etc. And, in the real world, a user can `act`: to select something from the shopping cart, to login, to logout etc.
Let's implement the same in Javascript using functions in properties. Let's implement the same in Javascript using functions in properties.
@ -65,22 +65,22 @@ user.sayHi(); // Hello!
That would also work, but is longer. Also we get an "extra" function `sayHi` outside of the `user` object. Usually we don't want that. That would also work, but is longer. Also we get an "extra" function `sayHi` outside of the `user` object. Usually we don't want that.
```smart header="Object-oriented programming" ```smart header="Object-oriented programming"
When we write our code using objects to represent entities, that's called an [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), in short: "OOP". When we write our code using objects to represent entities, that's called an [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), in short: "OOP".
OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture. OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture.
``` ```
### Method shorthand ### Method shorthand
There exists a shorter syntax for methods in an object literal: There exists a shorter syntax for methods in an object literal:
```js ```js
// these objects do the same // these objects do the same
let user = { let user = {
sayHi: function() { sayHi: function() {
alert("Hello"); alert("Hello");
} }
}; };
// method shorthand looks better, right? // method shorthand looks better, right?
let user = { let user = {
@ -89,10 +89,10 @@ let user = {
*/!* */!*
alert("Hello"); alert("Hello");
} }
}; };
``` ```
As demonstrated, we can omit `"function"` and just write `sayHi()`. As demonstrated, we can omit `"function"` and just write `sayHi()`.
To say the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases the shorter syntax is preferred. To say the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases the shorter syntax is preferred.
@ -128,10 +128,10 @@ Here during the execution of `user.sayHi()`, the value of `this` will be `user`.
Technically, it's also possible to access the object without `this`: Technically, it's also possible to access the object without `this`:
```js ```js
... ...
sayHi() { sayHi() {
alert( *!*user.name*/!* ); alert( *!*user.name*/!* );
} }
... ...
``` ```
@ -164,14 +164,14 @@ If we used `this.name` instead of `user.name` inside the `alert`, then the code
## "this" is not bound ## "this" is not bound
In Javascript, "this" keyword behaves unlike most other programming languages. First, it can be used in any function. In Javascript, "this" keyword behaves unlike most other programming languages. First, it can be used in any function.
There's no syntax error in the code like that: There's no syntax error in the code like that:
```js ```js
function sayHi() { function sayHi() {
alert( *!*this*/!*.name ); alert( *!*this*/!*.name );
} }
``` ```
The value of `this` is evaluated during the run-time. And it can be anything. The value of `this` is evaluated during the run-time. And it can be anything.
@ -210,16 +210,16 @@ function sayHi() {
sayHi(); sayHi();
``` ```
In this case `this` is `undefined` in strict mode. If we try to access `this.name`, there will be an error. In this case `this` is `undefined` in strict mode. If we try to access `this.name`, there will be an error.
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. 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. 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`" ```smart header="The consequences of unbound `this`"
If you come from another programming languages, then you are probably used to an idea of a "bound `this`", where methods defined in an object always have `this` referencing that object. If you come from another programming languages, then you are probably used to an idea of a "bound `this`", where methods defined in an object always have `this` referencing that object.
The idea of unbound, run-time evaluated `this` has both pluses and minuses. From one side, a function can be reused for different objects. From the other side, it's possible to occasionally loose `this` by making an improper call. The idea of unbound, run-time evaluated `this` has both pluses and minuses. From one side, a function can be reused for different objects. From the other side, greater flexibility opens a place for mistakes.
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. 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.
``` ```

View file

@ -3,7 +3,7 @@
In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives. In the chapter <info:type-conversions> we've seen the rules for numeric, string and boolean conversions of primitives.
But we left a gap for objects. Now let's fill it. But we left a gap for objects. Now let's close it. And, in the process, we'll see some built-in methods and the example of a built-in symbol.
[cut] [cut]

View file

@ -1,6 +1,8 @@
# Methods of primitives # 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. JavaScript allows to work with primitives (strings, numbers etc) as if they were objects.
They also provide methods to call and such. We are going to study them soon, but first let's see how it works, because, of course, primitives are not objects (and here we plan to make it even more clear).
[cut] [cut]
@ -10,13 +12,13 @@ A primitive
: Is a value of a primitive type. There are 6 primitive types: `string`, `number`, `boolean`, `symbol`, `null` and `undefined`. : Is a value of a primitive type. There are 6 primitive types: `string`, `number`, `boolean`, `symbol`, `null` and `undefined`.
An object An object
: Is capable of storing multiple values as properties. : 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. 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: One of the best thing about objects is that we can store a function as one of properties:
```js run ```js run
let john = { let john = {
name: "John", name: "John",
sayHi: function() { sayHi: function() {
alert("Hi buddy!"); alert("Hi buddy!");
@ -45,7 +47,7 @@ The solution looks a little bit awkward, but here it is.
1. Primitives are still primitive. A single value, as desired. 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. 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. 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. 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.
@ -77,7 +79,7 @@ let n = 1.23456;
alert( n.toFixed(2) ); // 1.23 alert( n.toFixed(2) ); // 1.23
``` ```
We'll see more specific methods in chapters <info:number> and <info:string>. We'll see more specific methods in chapters <info:number> and <info:string>.
````warn header="null/undefined have no methods" ````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". 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".
@ -92,4 +94,3 @@ alert(null.test); // error
- Primitives except `null` and `undefined` provide many helpful methods. We plan to study those in the next chapters. - 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. - 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.

View file

@ -1,4 +1,4 @@
# Strings # Strings
In JavaScript, the textual data is stored as strings. There is no separate type for a single character. In JavaScript, the textual data is stored as strings. There is no separate type for a single character.
@ -62,7 +62,7 @@ let guestList = "Guests:\n * John\n * Pete\n * Mary";
alert(guestList); // a multiline list of guests alert(guestList); // a multiline list of guests
``` ```
So to speak, these two lines describe the same: So to speak, these two lines describe the same:
```js run ```js run
alert( "Hello\nWorld" ); // two lines using a "newline symbol" alert( "Hello\nWorld" ); // two lines using a "newline symbol"
@ -92,7 +92,7 @@ alert( "\u{20331}" ); // 𠌱, a rare chinese hieroglyph (long unicode)
alert( "\u{1F60D}"); // a smiling face sumbol (another long unicode) alert( "\u{1F60D}"); // a smiling face sumbol (another long unicode)
``` ```
All special characters start with a backslash character `\`. It is also called an "escaping character". All special characters start with a backslash character `\`. It is also called an "escaping character".
We should also use it if we want to insert the quote into the string. We should also use it if we want to insert the quote into the string.
@ -107,7 +107,7 @@ See, we have to prepend the inner quote by the backslash `\'`, because otherwise
Of course, that refers only for the quotes that are same as the enclosing ones. So, as a more elegant solution, we could switch to double quotes or backticks instead: Of course, that refers only for the quotes that are same as the enclosing ones. So, as a more elegant solution, we could switch to double quotes or backticks instead:
```js run ```js run
alert( `I'm the Walrus!` ); // I'm the Walrus! alert( `I'm the Walrus!` ); // I'm the Walrus!
``` ```
Note that the backslash `\` serves for the correct reading of the string by Javascript, then disappears. The in-memory string has no `\`. You can clearly see that in `alert` from the examples above. Note that the backslash `\` serves for the correct reading of the string by Javascript, then disappears. The in-memory string has no `\`. You can clearly see that in `alert` from the examples above.
@ -123,7 +123,7 @@ alert( `The backslash: \\` ); // The backslash: \
## String length ## String length
The `length` property has the string length: The `length` property has the string length:
```js run ```js run
alert( `My\n`.length ); // 3 alert( `My\n`.length ); // 3
@ -134,7 +134,7 @@ Note that `\n` is a single "special" character, so the length is indeed `3`.
```warn header="`length` is a property" ```warn header="`length` is a property"
People with background in some other languages sometimes mistype by calling `str.length()` instead of just `str.length`. That doesn't work. People with background in some other languages sometimes mistype by calling `str.length()` instead of just `str.length`. That doesn't work.
Please note that `str.length` is a numeric property, not a function. There is no need to add brackets after it. Please note that `str.length` is a numeric property, not a function. There is no need to add brackets after it.
``` ```
## Accessing characters ## Accessing characters
@ -173,7 +173,7 @@ for(let char of "Hello") {
## Strings are immutable ## Strings are immutable
Strings can't be changed in JavaScript. It is impossible to change a character. Strings can't be changed in JavaScript. It is impossible to change a character.
Let's try to see that it doesn't work: Let's try to see that it doesn't work:
@ -219,7 +219,7 @@ There are multiple ways to look for a substring in a string.
### str.indexOf ### str.indexOf
The first method is [str.indexOf(substr, pos)](mdn:js/String/indexOf). The first method is [str.indexOf(substr, pos)](mdn:js/String/indexOf).
It looks for the `substr` in `str`, starting from the given position `pos`, and returns the position where the match was found or `-1` if nothing found. It looks for the `substr` in `str`, starting from the given position `pos`, and returns the position where the match was found or `-1` if nothing found.
@ -249,7 +249,7 @@ If we're interested in all occurences, we can run `indexOf` in a loop. Every new
```js run ```js run
let str = 'As sly as a fox, as strong as an ox'; let str = 'As sly as a fox, as strong as an ox';
let target = 'as'; // let's look for it let target = 'as'; // let's look for it
@ -258,7 +258,7 @@ while (true) {
let foundPos = str.indexOf(target, pos); let foundPos = str.indexOf(target, pos);
if (foundPos == -1) break; if (foundPos == -1) break;
alert( `Found at ${foundPos}` ); alert( `Found at ${foundPos}` );
pos = foundPos + 1; // continue the search from the next position pos = foundPos + 1; // continue the search from the next position
} }
``` ```
@ -302,7 +302,7 @@ let str = "Widget with id";
*!* *!*
if (str.indexOf("Widget") != -1) { if (str.indexOf("Widget") != -1) {
*/!* */!*
alert("We found it"); // works now! alert("We found it"); // works now!
} }
``` ```
@ -310,7 +310,7 @@ if (str.indexOf("Widget") != -1) {
````smart header="The bitwise NOT trick" ````smart header="The bitwise NOT trick"
One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation. One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation.
For 32-bit integers the call `~n` means exactly the same as `-(n+1)` (due to IEEE-754 format). For 32-bit integers the call `~n` means exactly the same as `-(n+1)` (due to IEEE-754 format).
For instance: For instance:
@ -344,7 +344,7 @@ Just remember: `if (~str.indexOf(...))` reads as "if found".
### includes, startsWith, endsWith ### includes, startsWith, endsWith
The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` has `substr` as its part. The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` has `substr` as its part.
It's the right choice if we need to test for the match, but don't need its position: It's the right choice if we need to test for the match, but don't need its position:
@ -373,7 +373,7 @@ alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
There are 3 methods in JavaScript to get a substring: `substring`, `substr` and `slice`. There are 3 methods in JavaScript to get a substring: `substring`, `substr` and `slice`.
`str.slice(start [, end])` `str.slice(start [, end])`
: Returns the part of the string from `start` to (but not including) `end`. : Returns the part of the string from `start` to (but not including) `end`.
For instance: For instance:
@ -398,12 +398,12 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
// start at the 4th position from the right, end at the 1st from the right // start at the 4th position from the right, end at the 1st from the right
alert( str.slice(-4, -1) ); // gif alert( str.slice(-4, -1) ); // gif
``` ```
`str.substring(start [, end])` `str.substring(start [, end])`
: Returns the part of the string *between* `start` and `end`. : Returns the part of the string *between* `start` and `end`.
Almost the same as `slice`, but allows `start` to be greater than `end`. Almost the same as `slice`, but allows `start` to be greater than `end`.
For instance: For instance:
@ -421,7 +421,7 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
``` ```
Negative arguments are (unlike slice) not supported, they are treated as `0`. Negative arguments are (unlike slice) not supported, they are treated as `0`.
`str.substr(start [, length])` `str.substr(start [, length])`
@ -511,14 +511,14 @@ let str = '';
for (let i = 65; i <= 220; i++) { for (let i = 65; i <= 220; i++) {
str += String.fromCodePoint(i); str += String.fromCodePoint(i);
} }
alert( str ); alert( str );
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„
// ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ
``` ```
See? Capital character go first, then few special ones, then lowercase characters. See? Capital character go first, then few special ones, then lowercase characters.
Now it becomes obvious why `a > Z`. Now it becomes obvious why `a > Z`.
The characters are compared by their numeric code. The greater code means that the character is greater. The code for `a` (97) is greater than the code for `Z` (90). The characters are compared by their numeric code. The greater code means that the character is greater. The code for `a` (97) is greater than the code for `Z` (90).
@ -548,21 +548,21 @@ For instance:
alert( 'Österreich'.localeCompare('Zealand') ); // -1 alert( 'Österreich'.localeCompare('Zealand') ); // -1
``` ```
The method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), that allow to specify the language (by default taken from the environment) and setup additional rules like case sensivity or should `"a"` and `"á"` be treated as the same etc. The method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), that allow to specify the language (by default taken from the environment) and setup additional rules like case sensivity or should `"a"` and `"á"` be treated as the same etc.
## Internals, Unicode ## Internals, Unicode
```warn header="Advanced knowledge" ```warn header="Advanced knowledge"
The section goes deeper into string internals. The knowledge will be useful for you if you plan to deal with emoji, rare mathematical of hieroglyphs characters or other rare symbols. The section goes deeper into string internals. The knowledge will be useful for you if you plan to deal with emoji, rare mathematical of hieroglyphs characters or other rare symbols.
You can skip the section if you don't plan to support them. You can skip the section if you don't plan to support them.
``` ```
### Surrogate pairs ### Surrogate pairs
Most symbols have a 2-byte code. Letters of most european languages, numbers, even most hieroglyphs have a 2-byte representation. Most symbols have a 2-byte code. Letters of most european languages, numbers, even most hieroglyphs have a 2-byte representation.
But 2 bytes only allow 65536 combinations that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair". But 2 bytes only allow 65536 combinations that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair".
The length of such symbols is `2`: The length of such symbols is `2`:
@ -574,7 +574,7 @@ alert( '𩷶'.length ); // 2, a rare chinese hieroglyph
Note that surrogate pairs did not exist at the time when Javascript was created, and thus are not correctly processed by the language! Note that surrogate pairs did not exist at the time when Javascript was created, and thus are not correctly processed by the language!
We actually have a single symbol in each of the strings above, but the `length` shows the length of `2`. We actually have a single symbol in each of the strings above, but the `length` shows the length of `2`.
`String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs. `String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs.
@ -651,7 +651,7 @@ alert( "S\u0307\u0323".normalize().length ); // 1
alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
``` ```
In real, that is not always so. The reason is that symbol `Ṩ` is "common enough", so UTF-16 creators included it into the main table and gave it the code. In real, that is not always so. The reason is that symbol `Ṩ` is "common enough", so UTF-16 creators included it into the main table and gave it the code.
If you want to learn more about normalization rules and variants -- they are described in the appendix to the Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), but for most practical reasons the information from this section is enough. If you want to learn more about normalization rules and variants -- they are described in the appendix to the Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), but for most practical reasons the information from this section is enough.
@ -659,7 +659,7 @@ If you want to learn more about normalization rules and variants -- they are des
## Summary ## Summary
- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions. - There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions.
- Strings in JavaScript are encoded using UTF-16. - Strings in JavaScript are encoded using UTF-16.
- We can use special characters like `\n` and insert letters by their unicode using `\u...`. - We can use special characters like `\n` and insert letters by their unicode using `\u...`.
- To get a character: use `[]`. - To get a character: use `[]`.
- To get a substring: use `slice` or `substring`. - To get a substring: use `slice` or `substring`.
@ -669,9 +669,8 @@ If you want to learn more about normalization rules and variants -- they are des
There are several other helpful methods in strings: There are several other helpful methods in strings:
- [str.trim()]` -- removes ("trims") spaces from the beginning and end of the string. - `str.trim()` -- removes ("trims") spaces from the beginning and end of the string.
- [str.repeat(n)]` -- repeats the string `n` times. - `str.repeat(n)` -- repeats the string `n` times.
- ...and others, see the [manual](mdn:js/String) for details. - ...and others, see the [manual](mdn:js/String) for details.
Also strings have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later. Also strings have methods for doing search/replace with regular expressions. But that topic deserves a separate chapter, so we'll return to that later.

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

View file

@ -320,9 +320,9 @@ But that's actually a bad idea. There are potential problems with it:
1. The loop `for..in` iterates over *all properties*, not only the numeric ones. 1. The loop `for..in` iterates over *all properties*, not only the numeric ones.
There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they have *other non-numeric properties and methods*, which we usually don't need in the loop. The `for..in` will list them. If we need to work with array-like objects, then these "extra" properties can become a problem. There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they may also have other non-numeric properties and methods, which we usually don't need. The `for..in` loop will list them though. So if we need to work with array-like objects, then these "extra" properties can become a problem.
2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup maybe important in few places bottlenecks or just irrelevant. But still we should be aware of the difference. 2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may matter only in bottlenecks or just irrelevant. But still we should be aware of the difference.
Generally, we shouldn't use `for..in` for arrays. Generally, we shouldn't use `for..in` for arrays.

View file

Before

Width:  |  Height:  |  Size: 6 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

@ -1,15 +0,0 @@
importance: 5
---
# Сколько секунд уже прошло сегодня?
Напишите функцию `getSecondsToday()` которая возвращает, сколько секунд прошло с начала сегодняшнего дня.
Например, если сейчас `10:00` и не было перехода на зимнее/летнее время, то:
```js
getSecondsToday() == 36000 // (3600 * 10)
```
Функция должна работать в любой день, т.е. в ней не должно быть конкретного значения сегодняшней даты.

View file

@ -1,34 +0,0 @@
function formatDate(date) {
var diff = new Date() - date; // number of ms till now
if (diff < 1000) { // less than a second
return 'right now';
}
var sec = Math.floor(diff / 1000); // get seconds
if (sec < 60) {
return sec + ' sec. ago';
}
var min = Math.floor(diff / 60000); // get minutes
if (min < 60) {
return min + ' min. ago';
}
// format the date, take into account that months start from zero
var d = date;
d = [
'0' + d.getDate(),
'0' + (d.getMonth() + 1),
'' + d.getFullYear(),
'0' + d.getHours(),
'0' + d.getMinutes()
];
for (var i = 0; i < d.length; i++) {
d[i] = d[i].slice(-2); // remove extra zeroes
}
return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':');
}

View file

@ -1,47 +0,0 @@
Для того, чтобы узнать время от `date` до текущего момента - используем вычитание дат.
```js run
function formatDate(date) {
var diff = new Date() - date; // разница в миллисекундах
if (diff < 1000) { // прошло менее 1 секунды
return 'только что';
}
var sec = Math.floor(diff / 1000); // округлить diff до секунд
if (sec < 60) {
return sec + ' сек. назад';
}
var min = Math.floor(diff / 60000); // округлить diff до минут
if (min < 60) {
return min + ' мин. назад';
}
// форматировать дату, с учетом того, что месяцы начинаются с 0
var d = date;
d = [
'0' + d.getDate(),
'0' + (d.getMonth() + 1),
'' + d.getFullYear(),
'0' + d.getHours(),
'0' + d.getMinutes()
];
for (var i = 0; i < d.length; i++) {
d[i] = d[i].slice(-2);
}
return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':');
}
alert( formatDate(new Date(new Date - 1)) ); // только что
alert( formatDate(new Date(new Date - 30 * 1000)) ); // 30 сек. назад
alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // 5 мин. назад
alert( formatDate(new Date(new Date - 86400 * 1000)) ); // вчерашняя дата в формате "дд.мм.гг чч:мм"
```

View file

@ -8,10 +8,10 @@ Arrays provide a lot of methods. To make things easier, in this chapter they are
We already know methods that add and remove items from the beginning or the end: We already know methods that add and remove items from the beginning or the end:
- `arr.push(...items)` - `arr.push(...items)` -- adds items to the end,
- `arr.pop()` - `arr.pop()` -- extracts an item from the end,
- `arr.shift(...items)` - `arr.shift(...items)` -- adds items to the beginning,
- `arr.unshift()` - `arr.unshift()` -- extracts an item from the beginning.
Here are few others. Here are few others.
@ -32,7 +32,7 @@ alert( arr[1] ); // undefined
alert( arr.length ); // 3 alert( arr.length ); // 3
``` ```
The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`. The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`.
That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now. That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now.
@ -330,7 +330,7 @@ alert(lengths); // 5,7,6
### sort(fn) ### sort(fn)
The method [arr.sort](mdn:js/Array/sort) sorts the array *at place*. The method [arr.sort](mdn:js/Array/sort) sorts the array *at place*.
For instance: For instance:
@ -351,7 +351,7 @@ The order became `1, 15, 2`. Incorrect. But why?
Literally, all elements are converted to strings and then compared. So, the lexicographic ordering is applied and indeed `"2" > "15"`. Literally, all elements are converted to strings and then compared. So, the lexicographic ordering is applied and indeed `"2" > "15"`.
To use our own sorting order, we need to supply a function of two arguments as the argument of `arr.sort()`. To use our own sorting order, we need to supply a function of two arguments as the argument of `arr.sort()`.
The function should work like this: The function should work like this:
```js ```js
@ -398,7 +398,7 @@ The algorithm may compare an element multiple times in the process, but it tries
````smart header="A comparison function may return any number" ````smart header="A comparison function may return any number"
Actually, a comparison function is only required to return a positive number to say "greater" and a negative number to say "less". Actually, a comparison function is only required to return a positive number to say "greater" and a negative number to say "less".
That allows to write shorter functions: That allows to write shorter functions:
@ -414,7 +414,7 @@ alert(arr); // *!*1, 2, 15*/!*
````smart header="Arrow functions for the best" ````smart header="Arrow functions for the best"
Remember [arrow functions](info:function-expression#arrow-functions)? We can use them here for neater sorting: Remember [arrow functions](info:function-expression#arrow-functions)? We can use them here for neater sorting:
```js ```js
arr.sort( (a, b) => a - b ); arr.sort( (a, b) => a - b );
``` ```
@ -489,7 +489,7 @@ When we need to iterate over an array -- we can use `forEach`.
When we need to iterate and return the data for each element -- we can use `map`. When we need to iterate and return the data for each element -- we can use `map`.
The methods [arr.reduce](mdn:js/Array/reduce) and [arr.reduceRight](mdn:js/Array/reduceRight) also belong to that breed, but are a little bit more intricate. They are used to calculate a single value based on the array. The methods [arr.reduce](mdn:js/Array/reduce) and [arr.reduceRight](mdn:js/Array/reduceRight) also belong to that breed, but are a little bit more intricate. They are used to calculate a single value based on the array.
The syntax is: The syntax is:
@ -552,14 +552,14 @@ We also can omit the initial value:
let arr = [1, 2, 3, 4, 5]; let arr = [1, 2, 3, 4, 5];
// removed initial value from reduce (no 0) // removed initial value from reduce (no 0)
let result = arr.reduce((sum, current) => sum + current); let result = arr.reduce((sum, current) => sum + current);
alert( result ); // 15 alert( result ); // 15
``` ```
The result is the same. That's because if there's no initial, then `reduce` takes the first element of the array as the initial value and starts the iteration from the 2nd element. The result is the same. That's because if there's no initial, then `reduce` takes the first element of the array as the initial value and starts the iteration from the 2nd element.
The calculation table is the same as above, minus the first row. The calculation table is the same as above, minus the first row.
But such use requires an extreme care. If the array is empty, then `reduce` call without initial value gives an error. So it's generally advised to specify the initial value. But such use requires an extreme care. If the array is empty, then `reduce` call without initial value gives an error. So it's generally advised to specify the initial value.
@ -567,7 +567,7 @@ The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes f
## Iterate: forEach ## Iterate: forEach
The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for every element of the array. The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for every element of the array.
@ -594,10 +594,10 @@ And this code is more elaborate about their positions in the target array:
``` ```
The result of the function (if it returns any) is thrown away and ignored. The result of the function (if it returns any) is thrown away and ignored.
## Array.isArray ## Array.isArray
Arrays do not form a separate language type. They are based on objects. Arrays do not form a separate language type. They are based on objects.
So `typeof` does not help to distinguish a plain object from an array: So `typeof` does not help to distinguish a plain object from an array:
@ -614,6 +614,9 @@ alert(Array.isArray({})); // false
alert(Array.isArray([])); // true alert(Array.isArray([])); // true
``` ```
```smart header="`Array.isArray` vs other type-checks"
You remembeare other ways to check for
## Methods: "thisArg" ## Methods: "thisArg"
Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`. Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`.

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

View file

@ -1,11 +1,11 @@
# Iterables # Iterables
*Iterable* objects is a general concept that allows to make any object useable in a `for..of` loop. *Iterable* objects is a generalization of arrays. That's a concept that allows to make any object useable in a `for..of` loop.
Many built-ins are partial cases of this concept. For instance, arrays are iterable. But not only arrays. Strings are iterable too. Arrays by themselves are iterable. But not only arrays. Strings are iterable too, and many other built-in objects as well.
Iterables come from the very core of Javascript and are widely used both in built-in methods and those provided by the environment. Iterables are widely used by the core Javascript, as we'll see many operators and built-in methods rely on them.
[cut] [cut]
@ -29,7 +29,7 @@ let range = {
To make the `range` iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). To make the `range` iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that).
- When `for..of` starts, it calls that method (or errors if none found). - When `for..of` starts, it calls that method (or errors if none found).
- The method must return an *iterator* -- an object with the method `next`. - The method must return an *iterator* -- an object with the method `next`.
- When `for..of` wants the next value, it calls `next()` on that object. - When `for..of` wants the next value, it calls `next()` on that object.
- The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` must be the new value. - The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` must be the new value.
@ -47,13 +47,13 @@ range[Symbol.iterator] = function() {
// 2. ...it returns the iterator: // 2. ...it returns the iterator:
return { return {
current: this.from, // remember "from" and "to" of the range current: this.from, // start at "range.from",
last: this.to, // in object properties last: this.to, // end at "range.to"
// 3. next() is called on each iteration of the loop // 3. next() is called on each iteration by for..of
next() { next() {
if (this.current <= this.last) { if (this.current <= this.last) {
// 4. iterator returns the value as an object {done:.., value :...} // 4. it should return the value as an object {done:.., value :...}
return { done: false, value: this.current++ }; return { done: false, value: this.current++ };
} else { } else {
return { done: true }; return { done: true };
@ -67,37 +67,51 @@ for (let num of range) {
} }
``` ```
There is an important separation of concerns in this code. There is an important separation of concerns in this code:
- The `range` itself does not have the `next()` method. - The `range` itself does not have the `next()` method.
- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`. - Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`, and it handles the iteration.
- It keeps the iteration state in its `current` property. That's good, because the original object is not modified by iterations. Also multiple `for..of` loops over the same object can run simultaneously, because they create separate iterators.
The fact that the object itself does not do the iteration also adds flexibility, because `range[Symbol.iterator]` can create iterators the smart way, depending on other object properties or external conditions. It's a full-fledged function that may be more complex than just a `return {...}`. So, the iterator is separate from the object.
Please note that the internal mechanics is not seen from outside. Here `for..of` calls `range[Symbol.iterator]()` and then the `next()` until `done: false`, but the external code doesn't see that. It only gets values. Technically, we may merge them and use `range` itself as the iterator, to make the code simpler.
Like this:
```js run
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
```
Now `range[Symbol.iterator]()` returns the `range` object itself, and it has the necessary `next()` method. Sometimes that's fine too. The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself.
```smart header="Infinite iterators" ```smart header="Infinite iterators"
Infinite iterators are also doable. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful. Infinite iterators are also doable. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful.
There are no limitations on `next`, it can return more and more values, that's normal. There are no limitations on `next`, it can return more and more values, that's normal.
Of course, the `for..of` loop over such an iterable would be endless, we'll need to stop if, for instance, using `break`. Of course, the `for..of` loop over such an iterable would be endless. But we can always stop it using `break`.
``` ```
````smart header="`Symbol.iterator` in a literal"
We could also write `Symbol.iterator` directly in the object literal, via computed properties syntax:
```js
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return {...};
}
};
```
````
## String is iterable ## String is iterable
@ -115,13 +129,16 @@ And it works right with surrogate pairs!
```js run ```js run
let str = '𝒳😂'; let str = '𝒳😂';
for(let char of str) { for(let char of str) {
alert(char); // 𝒳, and then 😂 alert(char); // 𝒳, and then 😂
} }
``` ```
````smart header="Calling an iterator manually" ## Calling an iterator explicitly
Iterators can also be created explicitly, without `for..of`, with a direct call of `Symbol.iterator`. For built-in objects too.
Normally, internals of iterables are hidden from the external code. There's a `for..of` loop, that works, that's all it needs to know.
But to understand things a little bit more deeper let's see how to create an iterator explicitly. We'll do that same as `for..of`, but with direct calls.
For instance, this code gets a string iterator and calls it "manually": For instance, this code gets a string iterator and calls it "manually":
@ -140,17 +157,16 @@ while(true) {
} }
``` ```
That is a little bit more flexible than `for..of`, because we can split the iteration process: iterate a bit, then stop, do something else, and then continue later. That is rarely needed, but gives us more control than `for..of`. For instance, we can split the iteration process: iterate a bit, then stop, do something else, and then resume later.
````
## Iterables and array-likes [#array-like] ## Iterables and array-likes [#array-like]
There are two official terms that are similar, but actually very different. Please be careful to avoid the confusion. There are two official terms that look similar, but are very different. Please be careful to avoid the confusion.
- Iterables are objects that implement the `Symbol.iterator` method, as described above. - *Iterables* are objects that implement the `Symbol.iterator` method, as described above.
- Array-likes are objects that have indexes and `length`, so they look like arrays. - *Array-likes* are objects that have indexes and `length`, so they look like arrays.
Sometimes they can both be applied. For instance, strings are both iterable and array-like. Naturally, they can combine. For instance, strings are both iterable and array-like.
But an iterable may be not array-like and vise versa. But an iterable may be not array-like and vise versa.
@ -171,7 +187,7 @@ for(let item of arrayLike) {}
*/!* */!*
``` ```
But what they share in common -- both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we received such object and want to work with it as with an array. What they share in common -- both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we have such object and want to work with it as with an array.
## Array.from ## Array.from
@ -186,10 +202,16 @@ let arrayLike = {
length: 2 length: 2
}; };
let arr = Array.from(arrayLike); *!*
let arr = Array.from(arrayLike); // (*)
*/!*
alert(arr.pop()); // World (method works) alert(arr.pop()); // World (method works)
``` ```
`Array.from` at the line `(*)` takes the object, examines it for being an iterable or array-like, then makes a new array and copies there all items.
The same happens for an iterable:
```js ```js
// assuming that range is taken from the example above // assuming that range is taken from the example above
let arr = Array.from(range); let arr = Array.from(range);
@ -209,9 +231,9 @@ For instance:
// assuming that range is taken from the example above // assuming that range is taken from the example above
// square each number // square each number
let arr = Array.from(range, num => num * num); let arr = Array.from(range, num => num * num);
alert(arr); // 1,4,9,16,25 alert(arr); // 1,4,9,16,25
``` ```
We can also use `Array.from` to turn a string into array of characters: We can also use `Array.from` to turn a string into array of characters:
@ -236,7 +258,7 @@ let str = '𝒳😂';
let chars = []; // Array.from internally does the same loop let chars = []; // Array.from internally does the same loop
for(let char of str) { for(let char of str) {
chars.push(char); chars.push(char);
} }
alert(chars); alert(chars);
@ -277,5 +299,3 @@ Objects that have indexed properties and `length` are called *array-like*. Such
If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract. If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract.
`Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and then we can use array methods on it. The optional arguments `mapFn` and `thisArg` allow to apply a function to each item. `Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and then we can use array methods on it. The optional arguments `mapFn` and `thisArg` allow to apply a function to each item.

View file

@ -0,0 +1,19 @@
That's because `map.keys()` returns an iterable, but not an array.
We can convert it into an array using `Array.from`:
```js run
let map = new Map();
map.set("name", "John");
*!*
let keys = Array.from(map.keys());
*/!*
keys.push("more");
alert(keys); // name, more
```

View file

@ -0,0 +1,24 @@
importance: 5
---
# Iterable keys
We want to get an array of `map.keys()` and go on working with it (apart from the map itself).
But there's a problem:
```js run
let map = new Map();
map.set("name", "John");
let keys = map.keys();
*!*
// Error: numbers.push is not a function
keys.push("more");
*/!*
```
Why? How can we fix the code to make `keys.push` work?

View file

@ -0,0 +1,41 @@
The sane choice here is a `WeakSet`:
```js
let messages = [
{text: "Hello", from: "John"},
{text: "How goes?", from: "John"},
{text: "See you soon", from: "Alice"}
];
let readMessages = new WeakSet();
// two messages have been read
readMessages.add(messages[0]);
readMessages.add(messages[1]);
// readMessages has 2 elements
// ...let's read the first message again!
readMessages.add(messages[0]);
// readMessages still has 2 unique elements
// answer: was the message[0] read?
alert("Read message 0: " + readMessages.has(messages[0])); // true
messages.shift();
// now readMessages has 1 element (technically memory may be cleaned later)
```
The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it.
It cleans up itself automatically. The tradeoff is that we can't iterate over it. We can't get "all read messages" directly. But we can do it by iterating over all messages and filtering those that are in the set.
P.S. Adding a property of our own to each message may be dangerous if messages are managed by someone else's code, but we can make it a symbol to evade conflicts.
Like this:
```js
// the symbolic property is only known to our code
let isRead = Symbol("isRead");
messages[0][isRead] = true;
```
Now even if someone else's code uses `for..in` loop for message properties, our secret flag won't appear.

Some files were not shown because too many files have changed in this diff Show more