This commit is contained in:
Ilya Kantor 2016-07-13 10:39:15 +03:00
parent 75cdcb406c
commit 4bdc4cd5aa
43 changed files with 1746 additions and 171 deletions

View file

@ -8,11 +8,11 @@ But in the browser, a user doesn't see the errors by default. So, if something g
To see errors and get a lot of other useful information about scripts, browsers have embedded "developer tools".
**It is recommended to use Chrome or Firefox for the development.**
**Most often developers lean towards Chrome or Firefox for the development.**
Other browsers also provide developer tools, but are usually in a "catching-up" position, compared to Chrome/Firefox which are the best.
If there is an error in the certain browser only, then we can use it's developer tools, but usually -- Chrome/Firefox.
If there is an error in the certain browser only, then we can always switch to it's developer tools for the concrete problem.
Developer tools are really powerful, there are many features. On this stage let's just look how to open them, look at errors and run JavaScript commands.

View file

@ -1,11 +1,18 @@
# Hello, world!
In this chapter we'll create a simple script and see it working.
About 98% of the tutorial is core Javascript, that is platform-independant. So you'll be able to learn how to use Node.JS and other things based on that knowledge.
But we need something like a "base environment" to run our scripts, and browser is probably a good choice.
So we'll start with attaching a script to the webpage. For other environments like Node.JS there are other ways to run it.
[cut]
[todo remove defer/async from here and move to 2nd part?]
## The "script" tag
[todo need this? and special (need it too?)]
```smart header="What if I want to move faster?"
In the case if you've developed with JavaScript already or have a lot of experience in another language, you can skip detailed explanatins and jump to <info:javascript-specials>. There you can find an essense of important features.

View file

@ -6,7 +6,7 @@ The first overall thing to know is the code structure.
## Statements
The code consists of [statements](https://en.wikipedia.org/wiki/Statement_(computer_science)) -- syntax constructs and commands to perform actions.
[Statements](https://en.wikipedia.org/wiki/Statement_(computer_science)) are syntax constructs and commands to perform actions.
We've already seen a statement `alert('Hello, world!')`, which shows the message.
@ -36,9 +36,9 @@ alert( 'Hello' )
alert( 'World' )
```
In this case JavaScript interprets the line break as a splitter. Just as if there were a semicolon between the lines.
Here JavaScript interprets the line break as an "implicit" semicolon. That's also called an [automatic semicolon insertion](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion).
**But it's important that "in most cases" does not mean "always"!**
**In most cases a newline implies a simicolon. But "in most cases" does not mean "always"!**
There are cases when a newline does not mean a semicolon, for example:
@ -50,12 +50,12 @@ alert(3 +
The code outputs `6`, because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression". And in this case that's actually fine and comfortable.
**But there are situations where JavaScript "fails" to assume a semicolon break where it is really needed.**
**But there are situations where JavaScript "fails" to assume a semicolon where it is really needed.**
Errors which come appear in such cases are quite hard to find and fix.
````smart header="An example of the error"
For a curious reader who might be interested in a concrete example, check this code out:
If you're curious to see a concrete example, check this code out:
```js run
[1, 2].forEach(alert)
@ -63,9 +63,9 @@ For a curious reader who might be interested in a concrete example, check this c
It shows `1` then `2`.
No need to think about the meaning of the brackets `[]` and `forEach` just for now -- it does not matter here. Let's just remember the result.
No need to think about the meaning of the brackets `[]` and `forEach`, for now -- it does not matter. Let's just remember the result.
Now we prepend an `alert` statement *not followed by a semicolon*:
Now we prepend the code with an `alert` statement *not followed by a semicolon*:
```js run no-beautify
alert( "There will be an error" ) // shown
@ -77,16 +77,16 @@ Now if we run it, only the first `alert` is shown, and then an error.
But everything's fine if we add a semicolon:
```js run
alert( "All fine now" ); // shown
[1, 2].forEach(alert) // then this too
[1, 2].forEach(alert) // this works too
```
The error in the former variant occurs because JavaScript engine does not assume a semicolon before square brackets `[...]`, so the code is actually treated as a one-line statement:
The error in the no-semicolon variant occurs because JavaScript engine does not assume a semicolon before square brackets `[...]`, so the code is actually treated as a one-line statement:
```js run no-beautify
alert( "There will be an error" )[1, 2].forEach(alert)
```
And in this particular case, that's just wrong. Hence the error.
And in this particular case, that's just wrong. There must be two independent statements. Hence the error.
````
@ -154,5 +154,5 @@ Please, don't hesitate to comment your code.
Comments increase the overall code footprint, but that's not a problem at all. There are many tools which minify the code before publishing to production server. They remove comments, so they do not appear in the working scripts. So, the comments do not have any negative effects on production at all.
Further in the tutorial we'll make more notes about how to write the code better, easier to read and maintain. We'll also talk more about comments.
Further in the tutorial we'll devote a special chapter to code style, also explaining how to write better comments.

View file

@ -58,10 +58,10 @@ Looking ahead let's just note that `"use strict"` can be put at the start of a f
It is recommended to always start a script with `"use strict"`, for the following reasons:
1. First, because all modern browsers support it, except Internet Explorer 9 and lower.
1. First, all modern browsers support it. Only outdated ones like Internet Explorer 9 and below do not.
2. Second, the modern JavaScript actually forces us into the strict mode. There are several modern language features like "classes" and "modules" that enable strict mode automatically. So, it's hard to evade it.
Here in the tutorial all code (except where said otherwise) works in `"use strict"`. but we'll still note the subtle differences of what happens if you forget it or if the visitor has an outdated browser. So you will also be able to write a code for IE8 and below if you'd like that.
Here in the tutorial all code (except where said otherwise) works in `"use strict"`. but we'll still note the subtle differences of what happens if you forget it or if the visitor has an outdated browser. So you will also be able to write a code that also works for old IE if you'd like that.
## Summary

View file

@ -1,5 +1,5 @@
We generally use upper case for constants that are "hard-coded" or, in other words, when the value is directly written into the code.
We generally use upper case for constants that are "hard-coded". Or, in other words, when the value is known prior to execution and directly written into the code.
In this code, `birthday` is just like that. So we could use the upper case for it.
In this code, `birthday` is exactly like that. So we could use the upper case for it.
In contrast, `age` is evaluated in run-time. Today we have one age, a year after we'll have another one. It is constant in a sense that it does not change through the code execution. But it is a bit "less of a constant" than `birthday`, so we should keep the lower case for it.
In contrast, `age` is evaluated in run-time. Today we have one age, a year after we'll have another one. It is constant in a sense that it does not change through the code execution. But it is a bit "less of a constant" than `birthday`, it is calculated, so we should keep the lower case for it.

View file

@ -9,18 +9,16 @@ Examine the following code:
```js
const birthday = '18.04.1982';
const age = calculateAgeBasedOn(birthday);
const age = someCode(birthday);
```
Here we have a constant `birthday` date and the `age` is calculated with the help of a function (its code is not provided cause it doesn't matter here).
Here we have a constant `birthday` date and the `age` is calculated from `birthday` with the help of some code (it is not provided for shortness, and because details don't matter here).
Would it be right to name both constants using the upper case? Like this:
Would it be right to use upper case for `birthday`? For `age`? Or even for both?
```js
const BIRTHDAY = '18.04.1982';
const BIRTHDAY = '18.04.1982'; // make uppercase?
const AGE = calculateAgeBasedOn(BIRTHDAY);
const AGE = someCode(BIRTHDAY); // make uppercase?
```
...Or we should use the upper case for only one of them? Or just use lower case everywhere?

View file

@ -1,8 +1,8 @@
# Variables
Most of the time, script needs to work with the information.
Most of the time, a script needs to work with the information.
If it's an online-shop -- that's going to be the goods and a shopping cart. If it's a chat -- visitors, messages and so on.
If it's an online-shop -- that's going to be the goods and a shopping cart. If it's a chat -- users, messages and so on.
Variables are used to store the information.
@ -10,7 +10,7 @@ Variables are used to store the information.
## A variable
A [variable]("https://en.wikipedia.org/wiki/Variable_(computer_science)") is defined as a "named storage" for the information. We can use variables to store the goods, visitors etc.
A [variable]("https://en.wikipedia.org/wiki/Variable_(computer_science)") is a basic "named storage" for the information. We can use variables to store the goods, visitors etc.
To create a variable in JavaScript, we need to use the `let` keyword.
@ -30,7 +30,7 @@ message = 'Hello'; // store the string
*/!*
```
The string is now saved into the memory area assosiated with that variable. We can access it using the variable name:
The string is now saved into the memory area assosiated with the variable. We can access it using the variable name:
```js run
let message;
@ -44,8 +44,9 @@ alert( message ); // shows the variable content
To be concise we can merge the variable declaration and assignment into a single line:
```js run
let message = 'Hello!';
alert( message ); // same as above
let message = 'Hello!'; // define the variable and assign the value
alert( message ); // Hello!
```
We can also declare multiple variables in one line:
@ -54,7 +55,7 @@ We can also declare multiple variables in one line:
let user = 'John', age = 25, message = 'Hello';
```
That might seem shorter, but it's recommended, for the sake of beter readability, to use a single line per variable.
That might seem shorter, but it's not recommended. For the sake of beter readability, please use a single line per variable.
The rewritten code is a bit longer, but easier to read:
@ -64,6 +65,24 @@ let age = 25;
let message = 'Hello';
```
Some people also write many variables like that:
```js no-beautify
let user = 'John',
age = 25,
message = 'Hello';
```
...Or even in comma-first style:
```js no-beautify
let user = 'John'
,age = 25
,message = 'Hello';
```
Technically, all these variants do the same. So, it's a matter of personal taste and aestetics.
````smart header="`var` instead of `let`"
In older scripts you may also find another keyword: `var` instead of `let`:
@ -231,9 +250,9 @@ myBirthday = '01.01.2001'; // error, can't reassign the constant!
When a programmer is sure that the variable should never change, he can use `const` to guarantee it, and also to clearly show that fact to everyone.
### Uppercases constants
### Uppercase constants
There is a widespread practice to use constants as aliases for difficult-to-remember and hard-coded prior to execution values.
There is a widespread practice to use constants as aliases for difficult-to-remember values that are known prior to execution.
Such constants are named using capitals and underscores.
@ -250,10 +269,7 @@ let color = COLOR_ORANGE;
alert( color ); // #FF7F00
```
`COLOR_ORANGE` is much easier to understand and remember than `"#FF7F00"`. Also it is much easier to make a typo in `"#FF7F00"` than in `COLOR_ORANGE`.
The upper case is only used for constants that are "hard-coded" (written in the code before its execution).
`COLOR_ORANGE` is much easier to remember than `"#FF7F00"`. Also it is much easier to mistype in `"#FF7F00"` than in `COLOR_ORANGE`. And when reading the code -- `COLOR_ORANGE` is much more meaningful.
## Name things right

View file

@ -49,7 +49,7 @@ Besides regular numbers there are so-called "special numeric values" which also
```smart header="Mathematical operations are safe"
Doing maths is safe in JavaScript. We can do anything: divide by zero, treat non-numeric strings as numbers, etc.
The script will never die. At worst we'll get `NaN` as the result.
The script will never stop ("die") on that. At worst we'll get `NaN` as the result.
```
Special numeric values formally belong to the "number" type. Of course they are not numbers in a common sense of this word.
@ -175,9 +175,9 @@ A `key` is a string, `value` can be anything.
For instance, here we create a `user` object with two properties:
```js
let user = {
name: "John",
age: 30
let user = { // an object
name: "John", // key "name" has value "John"
age: 30 // key "age" has value 30
};
```
@ -209,21 +209,22 @@ delete user.age;
![user object 3](object-user-delete.png)
If the string which denotes the key (also called a "property name") has multiple words, then we should use square brackets notation to access it:
```js
user["likes to swim?"] = true;
```
See, the dot requires the property name to be a valid variable identifier. That is: no spaces and other limitations. Square brackets work with any string.
If the string which denotes the key (also called a "property name") has multiple words, then the dot notation won't work:
```js
// this would give a syntax error
user.likes to swim? = true;
```
Another powerful feature of square bracket notation is that they allow to access a property by the name from the variable:
That's because, the dot requires the property name to be a valid variable identifier. That is: no spaces and other limitations.
There's a "square bracket notation" that works with any string:
```js
user["likes to swim?"] = true;
```
Square brackets are also the way to access a property by the name from the variable:
```js
let key = "likes to swim?";
@ -234,10 +235,41 @@ Here we have a variable `key` which contains the property name, probably evaluat
Most of time, the dot is used to access object properties, but when we need a complex property name or to pass the name as a variable, then -- we go square brackets.
Javascript supports object inheritance. There are many types that are based on objects: `Date` for dates, `Array` for ordered data, `Error` for error-reporting and so on. So the word "object" is applicable to a variety of things. The term *plain objects* or just `Object` (capital first) is used to represent "basic" objects, the ones we create with `{ ... }`.
What we've just seen is called a "plain object", or just `Object`.
There are many other kinds of objects in Javascript:
- `Array` to store ordered data collections,
- `Date` to store the information about the date and time,
- `Error` to store the information about an error.
- ...And so on.
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.
````smart header="A trailing comma"
[todo: move to 4-object ?]
Experienced developers sometimes add one more comma to the end of an object, like this:
```js
let user = {
name: "John",
age: 30,
isAdmin: true*!*,*/!* // extra comma
};
```
That's called a "trailing comma" and is allowed by the language.
Sometimes the reason is pure lazyness: when in the development process the last property becomes unneeded and is removed, the programmer forgets to delete the comma at the end of newly last one.
But from the other side that "lazyness" is justified, because the same line can be safely moved between objects -- from the first position to the middle or to the last -- without bookkeeping commas. That's a good thing.
Actual decision whether to add trailing commas or not depends on you. Some people like them, some find them ugly.
````
## Arrays
As weve just seen, objects in Javascript store arbitrary keyed values.
@ -271,7 +303,7 @@ Please note that arrays do not form a separate language type. They are based on
## Symbol type
The `symbol` type is used in conjunction with objects. Probably we won't need them any time soon, but it's the 7th and the last type of the language, so we must mention it here for the sake of completeness.
The `symbol` type is used in conjunction with objects. Probably we won't need them soon, but it's the 7th and the last type of the language, so we must mention it here for the sake of completeness.
A "symbol" represents an unique identifier with a given name.
@ -288,12 +320,14 @@ Symbols in JavaScript are different from symbols in Ruby language (if you are fa
let id1 = Symbol("id");
let id2 = Symbol("id");
*!*
alert(id1 == id2); // false
*/!*
```
Symbols is a special primitive type used for identifiers, which are guaranteed to be unique. So, even if we create many symbols with the same name, they are still unique.
The use case for symbols is to create "concealed" properties of an object, which only make sense locally, that no other part of code can occasionally access or overwrite.
The use case for symbols is to create "concealed" 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:
@ -309,7 +343,7 @@ Now let's imagine that another script wants to have his own "id" property inside
No problem. It can create its own `Symbol("id")`. There will be no conflict, because symbols are always different, even if they have the same name.
Please note that in the same case if we used a string `"id"` instead of a symbol here, then there would be a conflict:
Please note if we used a string `"id"` instead of a symbol for the same purpose, then there could be a conflict:
```js run
let user = { name: "John" };
@ -330,16 +364,16 @@ Symbols are widely used by the JavaScript language itself to store "system" prop
## The typeof operator [#type-typeof]
The `typeof` operator returns the type of the argument. It's handy in the case when we want to process values of different types differently, or just to make a quick check.
The `typeof` operator returns the type of the argument. It's useful when we want to process values of different types differently, or just want to make a quick check.
It allows two forms of syntax:
It supports two forms of syntax:
1. As an operator: `typeof x`.
2. Function style: `typeof(x)`.
In other words, it works both with the brackets or without them. They result is the same.
In other words, it works both with the brackets or without them. The result is the same.
The result of `typeof x` is a string, which has the type name:
The call to `typeof x` returns a string, which has the type name:
```js
typeof undefined // "undefined"
@ -367,10 +401,10 @@ typeof alert // "function" (3)
*/!*
```
Please note the last lines.
The last three lines may be a little unobvious so here's explanations:
1. The array is not a type of its own, but a subtype of object, that's why `typeof []` is `"object"`.
2. The result of `typeof null` equals to `"object"`. That's wrong. It is an officially recognized error in `typeof` implementation, kept for compatibility. Of course, `null` is not an object. It is a special value with a separate type of its own.
1. The array is not a type of its own, it is based on object, that's why `typeof []` is `"object"`.
2. The result of `typeof null` equals to `"object"`. That's wrong. It is an officially recognized error in `typeof`, kept for compatibility. Of course, `null` is not an object. It is a special value with a separate type of its own. So, again, that's an error in the language.
3. The result of `typeof alert` is `"function"`, because `alert` is a function of the language. We'll study functions in the near future and see that actually functions belong to the object type. But `typeof` treats them differently. That's very convenient in practice.

View file

@ -101,35 +101,45 @@ alert( '1' + 2 ); // '12' (string to the left)
alert( 1 + 2 ); // 3, numbers (for the contrast)
```
That only happens when one of arguments is a string, in other cases values are converted to numbers.
That only happens when one of arguments is a string. Otherwise values are converted to numbers.
````
## ToBoolean
Boolean conversion is the simplest one.
It happens in logical operations (later we'll meet `if` tests and other kinds), but also can be performed manually with the call of `Boolean(value)`.
It happens in logical operations (later we'll meet condition tests and other kinds), but also can be performed manually with the call of `Boolean(value)`.
The conversion rule:
- Values that are intuitively "empty", like `0`, an empty string, `null`, `undefined` and `NaN` become `false`.
- Other values become `true`.
For instance:
```js run
alert( Boolean(1) ); // true
alert( Boolean(0) ); // false
alert( Boolean("hello") ); // true
alert( Boolean("") ); // false
```
````warn header="Please note: the string with zero `\"0\"` is `true`"
Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript a non-empty string is always `true`.
```js run
alert( Boolean("0") ); // true
alert( Boolean(" ") ); // any non-empty string, even whitespaces are true
alert( Boolean(" ") ); // also true (any non-empty string is true)
```
````
````warn header="Empty objects and arrays are truthy"
````warn header="Empty objects are truthy"
All objects become `true`:
```js run
alert( Boolean([]) ); // true
alert( Boolean({}) ); // true
alert( Boolean([]) ); // true, even if empty array
alert( Boolean({}) ); // true, even if empty object
```
````
@ -138,9 +148,9 @@ alert( Boolean({}) ); // true
A string or numeric conversion of an object is a two-stage process. The object is first converted to a primitive value, and then ToString/ToNumber rules are applied to it.
The conversion is customizable on a per-object basis, so we'd better deal with it later when we know more about objects.
The conversion is customizable on a per-object basis, so we'll study it later when we go deeper into objects. [todo in the chapter?]
For now, let's just see two common rules that we often meet when showing objects.
Examples:
- When a plain object is converted into a string, is becomes `[object Object]`:
@ -160,8 +170,8 @@ For now, let's just see two common rules that we often meet when showing objects
We'll return to it in the chapter [todo].
```smart header="It was only about ToString/ToNumber"
For ToBoolean, there is no complexity neither customizability.
```smart header="ToBoolean is always true"
Here `ToBoolean` was not mentioned, because it provides no customizability for objects
The rule is simple: all objects are truthy.
```

View file

@ -214,6 +214,28 @@ alert( 8 % 3 ); // 2 is a remainder of 8 divided by 3
alert( 6 % 3 ); // 0 is a remainder of 6 divided by 3
```
## Exponentiation **
The exponentiation operator `**` is a recent addition to the language. Not all browsers support it yet.
For a natural number `b`, the result of `a ** b` is `a` multiplied by itself `b` times.
For instance:
```js run
alert( 2 ** 2 ); // 4 (2 * 2)
alert( 2 ** 3 ); // 8 (2 * 2 * 2)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2)
```
The operator works with non-integer numbers of `a` and `b` as well, for instance:
```js run
alert( 4 ** (1/2) ); // 2 (square root of 4)
alert( 8 ** (1/3) ); // 2 (cubic root of 8)
```
## Increment/decrement: ++, --
Increasing or decreasing a number by one is among the most common numerical operations.
@ -341,7 +363,7 @@ The list of operators:
- RIGHT SHIFT ( `>>` )
- ZERO-FILL RIGHT SHIFT ( `>>>` )
These operators are used very rarely. To understand them, we should delve into low-level number representation, and it would not be optimal to do that right now. Especially because we won't need them any time soon. If you're curious, you can read the [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) article in MDN. But it would be more practical to return to this topic later when a real need arises.
These operators are used very rarely. To understand them, we should delve into low-level number representation, and it would not be optimal to do that right now. Especially because we won't need them any time soon. If you're curious, you can read the [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) article in MDN. But it would be more practical to do that when a real need arises.
## Modify-in-place

View file

@ -144,31 +144,33 @@ Let's see more corner cases.
There's a non-intuitive behavior when `null` or `undefined` is compared with other values.
- For a strict equality check `===` these values are different, because each of them belong to a separate type of it's own.
- For a non-strict check `null == undefined`, there's a special rule. These two are a "sweet couple". They equal each other (in the sense of `==`), but no any other value.
For a strict equality check `===`
: These values are different, because each of them belong to a separate type of it's own.
- For maths and evaluation of other comparisons including `<`, `>`, `<=`, `>=`, values `null/undefined` are converted to a number: `null` becomes `0`, while `undefined` becomes `NaN`.
For a non-strict check `null == undefined`
: There's a special rule. These two are a "sweet couple": they equal each other (in the sense of `==`), but no any other value.
Now let's see funny things that happen when we apply those rules. And then, later, how do not fall into a trap with unobvious language features.
For maths and evaluation of other comparisons `< > <= >=`
: Values `null/undefined` are converted to a number: `null` becomes `0`, while `undefined` becomes `NaN`.
Now let's see funny things that happen when we apply those rules. And, what's more important, how do not fall into a trap with unobvious language features.
### Strange result: null vs 0
Let's compare `null` with a zero:
```js run
alert( null > 0 ); // false
alert( null == 0 ); // false
alert( null >= 0 ); // *!*true*/!*
alert( null > 0 ); // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) *!*true*/!*
```
Yeah, mathematically that's strange. The last result states that "`null` is equal or greater than zero". Then one of the comparisons above must be correct, but they are both falsy.
Well, every programmer language has its own unobvious features. That was an example for Javascript.
The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, hence treat it as `0`. That's why (1) `null >= 0` is true and (3) `null > 0` is false.
The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, hence treat it as `0`. That's why `null >= 0` is true and `null > 0` is false.
From the other hand, equality `==` for `undefined` and `null` works without any conversions. There's just a rule that they equal each other and don't equal anything else. That's why `null == 0` is false.
From the other hand, the equality check `==` for `undefined` and `null` works by the rule, without any conversions. They equal each other and don't equal anything else. That's why (2) `null == 0` is false.
### An uncomparable undefined
@ -195,19 +197,6 @@ Just treat any comparison with `undefined/null` except the strict equality `===`
Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you are really sure what you're doing. If a variable can have such values, then check it separately.
## Comparison with objects
For equality checks two objects are always treated as non-equal.
```js run
alert( {} == {} ); // false
alert( [] == [] ); // false
```
Note that Javascript does not try to compare the objects by content. The rule is simple: different objects are not equal.
For other comparisons like `< >` operators, or when an object is compared with a primitive, objects are first converted to primitives, and then the comparison runs as usual.
## Summary
- Comparison operators return a logical value.

View file

@ -2,6 +2,8 @@
This chapter covers basic UI operations: `alert`, `prompt` and `confirm`. They allow to ask a visitor for the input and show the results.
They are browser-specific. For other environments like Node.JS there are other ways of getting the information. Also they are very simple, so we can use them for the start.
[cut]
## alert

View file

@ -3,7 +3,7 @@
Objects in JavaScript combine two functionalities.
1. First -- they are "associative arrays": a structure for storing keyed data.
2. Second -- they provide features for object-oriented programming.
2. Second -- they provide features for object-oriented programming like inheritance.
Here we concentrate on the first part: using objects as a data store, and we will study it in-depth. That's the required base for studying the second part.
@ -41,14 +41,26 @@ let user = {
![](object-user-props.png)
````smart header="Trailing comma"
The last property 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/move/remove properties, because all lines become alike.
````
## Accessing a property
To access a property, there are two syntaxes:
- The dot notation: `user.name`
- Square brackets: `user["name"]`
Square brackets are more powerful, because they allow to specify arbitrary string as a property name. In contrast, the dot notation requires the nae to be a valid variable identifier, that is: no spaces, special chracters etc.
But more than that, square brackets is the only choice when the name of the property is in a variable.
The dot notation requires the name to be a valid variable identifier, that is: no spaces, special chracters etc. Square brackets are more powerful, because they allow to specify an arbitrary string as a property name. Also, square brackets is the only choice when the name of the property is in a variable.
For instance:
@ -60,12 +72,13 @@ let user = {
let key = prompt("What do you want to know about the user?", "name");
alert( user[key] ); // John (if enter "name"), 30 for the "age"
// access by variable
alert( user[key] ); // John (if enter "name")
```
The square brackets literally say: "take the property name from the variable".
The square brackets mean: "take the property name from the variable".
Also it is handy to use square brackets in an object literal, when the property name is stored in a variable.
Square brackets also can be used in an object literal.
That's called a *computed property*:
@ -73,7 +86,7 @@ That's called a *computed property*:
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5,
[fruit]: 5, // the name of the property is taken from the variable fruit
};
alert( bag.apple ); // 5 if fruit="apple"
@ -107,21 +120,12 @@ let obj = {
0: "test" // same as "0": "test"
}
// bot alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)
```
````
````smart header="Trailing comma"
The last property 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/move/remove property, 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.
@ -138,7 +142,9 @@ let obj = {
alert( obj.for + obj.let + obj.return ); // 6
```
Basically, any name is allowed. With one exclusion. There's a built-in property named `__proto__` with a special functionality (we'll cover it later), which can't be set to a non-object value:
Basically, any name is allowed, with one exclusion: `__proto__`.
The built-in property named `__proto__` has a special functionality (we'll cover it later), and it can't be set to a non-object value:
```js run
let obj = {};
@ -146,7 +152,7 @@ obj.__proto__ = 5;
alert(obj.__proto__); // [object Object], didn't work as intended
```
If we want to store *arbitrary* (user-provided) keys, then this can be a source of bugs. There's another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in a few chapters, it can support arbitrary keys.
As you we see from the code, an assignment to a primitive 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.
````
## Removing a property
@ -194,23 +200,25 @@ let key = "age";
alert( key in user ); // true, takes the value of key and checks for such property
```
````smart header="The property which equals `undefined`"
Thee is a case when `"=== undefined"` check fails, but the `"in"` operator works correctly.
````smart header="Using \"in\" for properties that store `undefined`"
There is a case when `"=== undefined"` check fails, but the `"in"` operator works correctly.
It's when an object property stores `undefined`:
```js run
let obj = { test: undefined };
let obj = {
test: undefined
};
alert( obj.test ); // undefined, no such property?
alert( "test" in obj ); // true, the property does exist!
```
In the code above, the property `obj.test` stores `undefined`, so the first check fails.
But the `in` operator puts things right.
Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for unknown values. So the `in` operator is an exotic guest in the code.
In the code above, the property `obj.test` technically exists. So the `in` operator works right.
Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code.
````
## Property shorthands
@ -245,7 +253,6 @@ Methods definitions
};
```
To say the truth, these notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter.
## Loops
@ -395,12 +402,37 @@ alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
Quite obvious, if we used one of the keys (`admin`) and changed something inside the cabinet, then if we use another key later (`user`), we find things modified.
### Comparison with objects
## Cloning objects
Two objects are equal only when they are one object:
```js run
let a = {};
let b = a; // copy the reference
alert( a == b ); // true, both variables reference the same object
```
We can also think of it like: the variables are "papers with address" of the objects. We copied the address from `a` to `b`. Then when we compare `a == b`, we compare the adresses. If they match, the equality is truthy.
In all other cases objects are non-equal, even if their content is the same.
For instance:
```js run
let a = {};
let b = {}; // two independents object
alert( a == b ); // false
```
For unusual equality checks like: object vs a priimtive, or an object less/greater `< >` than another object, objects are converted to numbers. To say the truth, such comparisons occur very rarely in real code and usually are a result of a mistake.
## Cloning, Object.assign
What if we need to duplicate an object? Create an independant copy, a clone?
That's also doable, but a little bit more difficult, because there's no such method in Javascript. Frankly, that's very rarely needed.
That's also doable, but a little bit more difficult, because there's no such method in Javascript. Actually, copying by reference is good most of the time.
But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.
@ -498,14 +530,14 @@ Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes`
To fix that, we should examine the value of `user[key]` in the cloning loop 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). We can use a ready implementation from the Javascript library [lodash](https://lodash.com). The method is [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
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). We can use a ready implementation of it from the Javascript library [lodash](https://lodash.com). The method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
## Ordering
Are objects ordered? If we loop over an object, do we get all properties in the same order that they are in the `{...}` definition?
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?
The answer is "yes" for non-numeric properties, "no" for others.
The short answer is: "no" for integer properties, "yes" for others. The details follow.
As an example, let's consider an object with the phone codes:
@ -519,29 +551,40 @@ let codes = {
};
*!*
for(let code in codes) alert(code); // 1, 41, 44, 49
for(let code in codes) {
alert(code); // 1, 41, 44, 49
}
*/!*
```
The object is used to generate HTML `<select>` list. If we're making a site mainly for German audience then we probably want `49` to be the first.
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 try to loop over the object, 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.
That's because according to the language stantard objects have no order. The loop is officially allowed to list properties randomly.
That's because the iteration order is:
But in practice, there's a de-facto agreement among modern JavaScript engines.
1. Integer properties in the ascending sort order go first.
2. String properties in the orders of their creation.
3. Symbol properties in the order of their creation.
- The numeric properties are sorted.
- Non-numeric properties are ordered as they appear in the object.
The phone codes were sorted, because they are integer. That's why we see `1, 41, 44, 49`.
That agreement is not enforced by a standard, but stands strong, because a lot of JavaScript code is already based on it.
````smart header="Integer properties? What's that?"
By specification object property names are either strings or symbols. So an "integer property" actually means a string that can be converted to-from integer without a change.
Now it's easy to see that the properties were iterated in the ascending order, because they are numeric... Of course, object property names are strings, but the Javascript engine detects that it's a number and applies internal optimizations to it, including sorting. That's why we see `1, 41, 44, 49`.
So, "49" is an integer string, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not:
On the other hand, if the keys are non-numeric, then they are listed as they appear, for instance:
```js run
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
```
````
On the other hand, if the keys are non-integer, then they are listed as they appear, for instance:
```js run
let user = {
@ -551,14 +594,14 @@ let user = {
user.age = 25; // add one more
*!*
// as they appear in the object
// non-integer properties are listed in the creation order
*/!*
for (let prop in user) {
alert( prop ); // name, surname, age
}
```
So, to fix the issue with the phone codes, we can "cheat" by making the codes non-numeric. Adding a plus `"+"` sign before each code is enough.
So, to fix the issue with the phone codes, we can "cheat" by making the codes non-integer. Adding a plus `"+"` sign before each code is enough.
Like this:
@ -572,8 +615,7 @@ let codes = {
};
for(let code in codes) {
// explicitly convert each code to a number if required
alert( +code ); // 49, 41, 44, 1
alert( +code ); // 49, 41, 44, 1
}
```
@ -599,8 +641,8 @@ Property access:
- `for([key,value] of Object.entries(obj))` for both.
- Ordering:
- Non-numeric properties keep the order.
- Numeric properties are sorted. To keep the order for numeric properties, we can prepend them with `+` to make them look like non-numeric.
- 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.

View file

@ -22,10 +22,12 @@ readMessages.add(messages[0]);
alert("Read message 0: " + readMessages.has(messages[0]));
messages.shift();
// now readMessages has 1 element (or will be cleaned soon)
// 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. That's not needed here though.
P.S. `WeakSet` stores one bit of information: "was it read" (yes/no - in set/out of set). To store a date, we can use `WeakMap`.

View file

@ -16,6 +16,8 @@ let messages = [
Your code can access it, but the messages are managed by someone else's code. New messages are added, old ones are removed regularly by that code, and you don't know the exact moments when it happens.
Now, which data structure you could use to store only messages that are "have been read"? The structure must be well-suited to give the answer "was it read?" for the given message.
Now, which data structure you could use to store only messages that are "have been read"? The structure must be well-suited to give the answer "was it read?" for the given message object.
P.S. When a message is removed from the collection, it should disappear from your structure as well.
P.P.S. What you'd use to store the information about "when it was read?"

View file

@ -246,7 +246,7 @@ For instance:
```js
let john = { name: "John" };
// the object can be accessed, john is the reference
// the object can be accessed, john is the reference to it
// overwrite the reference
john = null;
@ -254,62 +254,93 @@ john = null;
// the object will be removed from memory
```
We'll go into more details later, but the gist is somewhat obvious, right? If nothing references the object, it can be safely removed.
We'll go into more details later, but the example here is somewhat obvious, right? If nothing references the object, it can be safely removed.
Usually, if an object is in a set or an array or another data structure, and the data structure is in memory, then the object remains in memory too. With the exception of `WeakMap/WeakSet`.
**If an object only exists in `WeakMap/WeakSet`, then it is removed from the memory.**
**`WeakMap/WeakSet` does not prevent the object removal from the memory.**
That's handy for situations when we have a main storage for the objects somewhere and need to keep additional data for them that only exists while the object exists.
For instance, let's start with `WeakMap`. The first difference from `Map` is that its keys can only be objects, not primitive values:
For instance, we have a code that keeps a visit count for each user. And when the user leaves, we don't need to store his visit count any more.
One way would be to keep track of leaving users and cleaning up the storage manually.
```js run
let weakMap = new WeakMap();
Another way would be to use `WeakMap`:
let obj = {};
weakMap.set(obj, "ok"); // works fine (object key)
*!*
weakMap.set("test", "wops"); // Error, because "test" is a primitive
*/!*
```
So it stores data for objects. And the data only exists while the object exists. That's handy for situations when we have a main storage for the objects somewhere and need to keep additional information that is only relevant while the object lives.
Let's see an example.
For instance, we have a code that keeps a visit count for each user. The information is stored in a map: a user is the key and the visit count is the value. When a user leaves, we don't want to store his visit count any more.
One way would be to keep track of leaving users and clean up the storage manually:
```js run
let john = { name: "John" };
// user => visits count
let visitsCountMap = new WeakMap();
// map: user => visits count
let visitsCountMap = new Map();
// john is the key for the map
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
// now john leaves us, we don't need him any more
john = null;
*!*
alert( visitsCountMap.get(john) ); // undefined
// but it's still in the map, we need to clean it!
*/!*
alert( visitsCountMap.size ); // 1
// it's also in the memory, because Map uses it as the key
```
Did you notice the difference versus a regular `Map`? If `visitorCountMap` were a `new Map()`, then the object `john` and the corresponding value would remain in it. If users keep coming and leaving, then there would be more and more values flooding the map.
Another way would be to use `WeakMap`:
So, with a regular `Map`, the user deletion becomes a more tedious task: we also need to clean up the additional stores. And it can become rather cumbersome in more complex cases when users are managed in one place of the code and the additional structure is at another place and is getting no information about removals.
```js
let john = { name: "John" };
`WeakMap` uses only objects as keys, not primitive values. It has the following methods:
let visitsCountMap = new WeakMap();
visitsCountMap.set(john, 123);
// now john leaves us, we don't need him any more
john = null;
// there are no references except WeakMap,
// so the object is removed both from the memory and from visitsCountMap automatically
```
With a regular `Map`, the user deletion becomes a tedious task: we not only need to remove the user from it's main storage (be it a variable or an array), but also need to clean up the additional stores like `visitsCountMap`. And it can become cumbersome in more complex cases when users are managed in one place of the code and the additional structure is at another place and is getting no information about removals.
`WeakMap` can make things simpler, because it is cleaned up automatically.
`WeakMap` has only the following methods:
- `weakMap.get(key)`
- `weakMap.set(key, value)`
- `weakMap.delete(key, value)`
- `weakMap.has(key)`
`WeakMap` does not support methods `clear()` and has no `size` property. Also we can not iterate over it.
Please note `WeakMap` does not support methods `clear()` and has no `size` property. Also we can not iterate over it.
That's for technical reasons. Once a key is no longer referenced from anywhere, it is removed from the `WeakMap` and from the memory. But technically it's not exactly specified *when the removal happens*.
That's for technical reasons. If the object is to be removed (like `john` in the code above), then it `WeakMap` will be cleaned up automatically. But technically it's not exactly specified *when the cleanup happens*.
The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or wait and do the cleaning later when more deletions happen. So, technically the current element count of the `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, the methods that access the `WeakMap` as a whole are not supported.
The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of the `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, the methods that access the `WeakMap` as a whole are not supported.
The same refers to `WeakSet`.
- It keeps a set of objects, an object exists while it is referenced from anywhere else.
- It only supports `add`, `has` and `delete`, no support for `size` and no iterations.
- Like `Set`, it supports `add`, `has` and `delete`, but not `size` and no iterations.
The limitations may appear inconvenient, but they actually do not prevent `WeakMap/WeakSet` from doing the main task -- be an "additional" storage of data for objects which are stored/managed at another place.
The limitations may appear inconvenient, but they actually do not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place.
## Summary
@ -319,11 +350,12 @@ The limitations may appear inconvenient, but they actually do not prevent `WeakM
- Any keys, objects can be keys.
- Iterates in the insertion order.
- Additional convenient methods for iterating and cleaning, the `size` property.
- Additional convenient methods, the `size` property.
- `Set` -- is a collection of unique values.
- Unlike an array, does not allow to reorder elements. Keeps the insertion order.
- Unlike an array, does not allow to reorder elements.
- Keeps the insertion order.
- `WeakMap` -- a variant of `Map` that allows only objects as keys and removes them once they become unaccessible by other means.
@ -331,5 +363,7 @@ The limitations may appear inconvenient, but they actually do not prevent `WeakM
- `WeakSet` -- is a variant of `Set` that only stores objects and removes them once they become unaccessible by other means.
`WeakMap` and `WeakSet` are used as "secondary" data structures in additional to the "main" object storage. Once the object is removed from the main storage, they clean up aumatically.
- Also does not support `size/clear()` and iterations.
`WeakMap` and `WeakSet` are used as "secondary" data structures in additional to the "main" object storage. Once the object is removed from the main storage, so it only stays in `WeakMap/WeakSet`, they clean up aumatically.

View file

@ -0,0 +1,42 @@
Решение **с использованием цикла**:
```js run
function sumTo(n) {
var sum = 0;
for (var i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
alert( sumTo(100) );
```
Решение через **рекурсию**:
```js run
function sumTo(n) {
if (n == 1) return 1;
return n + sumTo(n - 1);
}
alert( sumTo(100) );
```
Решение **по формуле**: `sumTo(n) = n*(n+1)/2`:
```js run
function sumTo(n) {
return n * (n + 1) / 2;
}
alert( sumTo(100) );
```
P.S. Надо ли говорить, что решение по формуле работает быстрее всех? Это очевидно. Оно использует всего три операции для любого `n`, а цикл и рекурсия требуют как минимум `n` операций сложения.
Вариант с циклом -- второй по скорости. Он быстрее рекурсии, так как операций сложения столько же, но нет дополнительных вычислительных затрат на организацию вложенных вызовов.
Рекурсия в данном случае работает медленнее всех.
P.P.S. Существует ограничение глубины вложенных вызовов, поэтому рекурсивный вызов `sumTo(100000)` выдаст ошибку.

View file

@ -0,0 +1,34 @@
importance: 5
---
# Вычислить сумму чисел до данного
Напишите функцию `sumTo(n)`, которая для данного `n` вычисляет сумму чисел от 1 до `n`, например:
```js no-beautify
sumTo(1) = 1
sumTo(2) = 2 + 1 = 3
sumTo(3) = 3 + 2 + 1 = 6
sumTo(4) = 4 + 3 + 2 + 1 = 10
...
sumTo(100) = 100 + 99 + ... + 2 + 1 = 5050
```
Сделайте три варианта решения:
1. С использованием цикла.
2. Через рекурсию, т.к. `sumTo(n) = n + sumTo(n-1)` для `n > 1`.
3. С использованием формулы для суммы [арифметической прогрессии](http://ru.wikipedia.org/wiki/%D0%90%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B5%D1%81%D1%81%D0%B8%D1%8F).
Пример работы вашей функции:
```js
function sumTo(n) { /*... ваш код ... */ }
alert( sumTo(100) ); // 5050
```
**Какой вариант решения самый быстрый? Самый медленный? Почему?**
**Можно ли при помощи рекурсии посчитать `sumTo(100000)`? Если нет, то почему?**

View file

@ -0,0 +1,23 @@
По свойствам факториала, как описано в условии, `n!` можно записать как `n * (n-1)!`.
То есть, результат функции для `n` можно получить как `n`, умноженное на результат функции для `n-1`, и так далее до `1!`:
```js run
function factorial(n) {
return (n != 1) ? n * factorial(n - 1) : 1;
}
alert( factorial(5) ); // 120
```
Базисом рекурсии является значение `1`. А можно было бы сделать базисом и `0`. Тогда код станет чуть короче:
```js run
function factorial(n) {
return n ? n * factorial(n - 1) : 1;
}
alert( factorial(5) ); // 120
```
В этом случае вызов `factorial(1)` сведётся к `1*factorial(0)`, будет дополнительный шаг рекурсии.

View file

@ -0,0 +1,31 @@
importance: 4
---
# Вычислить факториал
*Факториа́л числа* -- это число, умноженное на "себя минус один", затем на "себя минус два" и так далее, до единицы. Обозначается `n!`
Определение факториала можно записать как:
```js
n! = n * (n - 1) * (n - 2) * ...*1
```
Примеры значений для разных `n`:
```js
1! = 1
2! = 2 * 1 = 2
3! = 3 * 2 * 1 = 6
4! = 4 * 3 * 2 * 1 = 24
5! = 5 * 4 * 3 * 2 * 1 = 120
```
Задача -- написать функцию `factorial(n)`, которая возвращает факториал числа `n!`, используя рекурсивный вызов.
```js
alert( factorial(5) ); // 120
```
Подсказка: обратите внимание, что `n!` можно записать как `n * (n-1)!`. Например: `3! = 3*2! = 3*2*1! = 6`

View file

@ -0,0 +1,93 @@
# Вычисление рекурсией (медленное)
Решение по формуле, используя рекурсию:
```js run
function fib(n) {
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
alert( fib(3) ); // 2
alert( fib(7) ); // 13
// fib(77); // не запускаем, подвесит браузер
```
При больших значениях `n` оно будет работать очень медленно. Например, `fib(77)` уже будет вычисляться очень долго.
Это потому, что функция порождает обширное дерево вложенных вызовов. При этом ряд значений вычисляется много раз. Например, посмотрим на отрывок вычислений:
```js no-beautify
...
fib(5) = fib(4) + fib(3)
fib(4) = fib(3) + fib(2)
...
```
Здесь видно, что значение `fib(3)` нужно одновременно и для `fib(5)` и для `fib(4)`. В коде оно будет вычислено два раза, совершенно независимо.
Можно это оптимизировать, запоминая уже вычисленные значения, получится гораздо быстрее. Альтернативный вариант -- вообще отказаться от рекурсии, а вместо этого в цикле начать с первых значений `1`, `2`, затем из них получить `fib(3)`, далее `fib(4)`, затем `fib(5)` и так далее, до нужного значения.
Это решение будет наиболее эффективным. Попробуйте его написать.
# Алгоритм вычисления в цикле
Будем идти по формуле слева-направо:
```js no-beautify
var a = 1, b = 1; // начальные значения
var c = a + b; // 2
/* переменные на начальном шаге:
a b c
1, 1, 2
*/
```
Теперь следующий шаг, присвоим `a` и `b` текущие 2 числа и получим новое следующее в `c`:
```js no-beautify
a = b, b = c;
c = a + b;
/* стало так (ещё число):
a b c
1, 1, 2, 3
*/
```
Следующий шаг даст нам ещё одно число последовательности:
```js no-beautify
a = b, b = c;
c = a + b;
/* стало так (ещё число):
a b c
1, 1, 2, 3, 5
*/
```
Повторять в цикле до тех пор, пока не получим нужное значение. Это гораздо быстрее, чем рекурсия, хотя бы потому что ни одно из чисел не вычисляется дважды.
P.S. Этот подход к вычислению называется [динамическое программирование снизу-вверх](http://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5).
# Код для вычисления в цикле
```js run
function fib(n) {
var a = 1,
b = 1;
for (var i = 3; i <= n; i++) {
var c = a + b;
a = b;
b = c;
}
return b;
}
alert( fib(3) ); // 2
alert( fib(7) ); // 13
alert( fib(77) ); // 5527939700884757
```
Цикл здесь начинается с `i=3`, так как первое и второе числа Фибоначчи заранее записаны в переменные `a=1`, `b=1`.

View file

@ -0,0 +1,24 @@
importance: 5
---
# Числа Фибоначчи
Последовательность [чисел Фибоначчи](http://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8) имеет формулу <code>F<sub>n</sub> = F<sub>n-1</sub> + F<sub>n-2</sub></code>. То есть, следующее число получается как сумма двух предыдущих.
Первые два числа равны `1`, затем `2(1+1)`, затем `3(1+2)`, `5(2+3)` и так далее: `1, 1, 2, 3, 5, 8, 13, 21...`.
Числа Фибоначчи тесно связаны с [золотым сечением](http://ru.wikipedia.org/wiki/%D0%97%D0%BE%D0%BB%D0%BE%D1%82%D0%BE%D0%B5_%D1%81%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5) и множеством природных явлений вокруг нас.
Напишите функцию `fib(n)`, которая возвращает `n-е` число Фибоначчи. Пример работы:
```js no-beautify
function fib(n) { /* ваш код */ }
alert( fib(3) ); // 2
alert( fib(7) ); // 13
alert( fib(77)); // 5527939700884757
```
**Все запуски функций из примера выше должны срабатывать быстро.**

View file

@ -0,0 +1,277 @@
# Recursion and stack
A function solves a task. In the process it can call many other functions. A partial case of this is when a function calls *itself*.
That's called *a recursion*.
Recursion is useful in situations when a task can be naturally split into several tasks of the same kind, but simpler. Or when a task can be simplified into an easy action plus a simpler variant of the same task.
Soon we'll see examples.
Recursion is a common programming topic, not specific to Javascript. If you developed in other languages or studied algorithms, then you probably already know what's this.
The chapter is mostly meant for those who are unfamiliar with the topic and want to study better how functions work.
[cut]
## Recursive thinking
For something simple to start with -- let's consider a task of raising `x` into a natural power of `n`. In other words, we need `x` multiplied by itself `n` times.
There are two ways to solve it.
1. Iterative thinking -- the `for` loop:
```js run
function pow(x, n) {
let result = 1;
// multiply result by x n times in the loop
for(let i = 0; i < n; i++) {
result *= x;
}
return result
}
alert( pow(2, 3) ); // 8
```
2. Recursive thinking -- simplify the task:
```js run
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
alert( pow(2, 3) ); // 8
```
The recursive variant represents the function as a simple action plus a simplified task:
```js
if n==1 = x
/
pow(x, n) =
\ = x * pow(x, n - 1)
else
```
Here we have two branches.
1. The `n==1` is the trivial variant. It is called *the base* of recursion, because it immediately produces the obvious result: `pow(x, 1)` equals `x`.
2. Otherwise, we can represent `pow(x, n)` as `x * pow(x, n-1)`. In maths, one would write <code>x<sup>n</sup> = x * x<sup>n-1</sup></code>. This is called *a recursive step*, because we transform the task into a simpler action (multiplication by `x`) and a simpler call of the same task (`pow` with lower `n`). Next steps simplify it further and further untill `n` reaches `1`.
We can also say that `pow` *recursively calls itself* till `n == 1`.
For example, to calculate `pow(2, 4)` the recursive variant does these steps:
1. `pow(2, 4) = 2 * pow(2, 3)`
2. `pow(2, 3) = 2 * pow(2, 2)`
3. `pow(2, 2) = 2 * pow(2, 1)`
4. `pow(2, 1) = 2`
````smart header="Recursion is usually shorter"
A recursive solution is usually shorter.
Here we can write the same using the ternary `?` operator instead of `if` to make it even more terse and still very readable:
```js run
function pow(x, n) {
return (n == 1) ? x : (x * pow(x, n-1));
}
```
````
So, the recursion can be used when a function call can be reduced to a simpler call, and then -- to even more simpler, and so on, until the result becomes obvious.
The maximal number of nested calls (including the first one) is called *recursion depth*. In our case, it there will be exactly `n`.
The maximal recursion depth is limited by Javascript engine. We can make sure about 10000, some engines allow more, but 100000 is probably out of limit for the majority of them. There are automatic optimizations that help to alleviate this ("tail calls optimizations"), but they are not yet supported everywhere and work only in simple cases.
That limits the application of recursion, but it still remains very wide. There are many tasks where a recursive way of thinking gives simpler code, easier to maintain.
## The execution stack
Now let's examine how recursive calls work. For that we'll look under the hood of functions, how they work.
The information about a function run is stored in its *execution context*.
The [execution context](https://tc39.github.io/ecma262/#sec-execution-contexts) is an internal data structure that contains details about where the execution flow is now, current variables, the value of `this` (we don't use it here) and few other internal details.
### pow(2, 3)
For instance, for the beginning of the call `pow(2, 3)` the execution context will store variables: `x = 2, n = 3`, the execution flow is at line `1` of the function.
We can sketch it as:
<ul class="function-execution-context-list">
<li>
<span class="function-execution-context">Context: { x: 2, n: 3, at line 1 }</span>
<span class="function-execution-context-call">pow(2, 3)</span>
</li>
</ul>
That's when the function starts to execute. The condition `n == 1` is falsy, so the flow continues into the second branch of `if`:
```js run
function pow(x, n) {
if (n == 1) {
return x;
} else {
*!*
return x * pow(x, n - 1);
*/!*
}
}
alert( pow(2, 3) );
```
The line changes, so the context is now:
<ul class="function-execution-context-list">
<li>
<span class="function-execution-context">Context: { x: 2, n: 3, at line 5 }</span>
<span class="function-execution-context-call">pow(2, 3)</span>
</li>
</ul>
To calculate `x * pow(x, n-1)`, we need to run `pow` witn new arguments.
### pow(2, 2)
To do a nested call, Javascript remembers the current execution context in the *execution context stack*.
Here we call the same function `pow`, but it absolutely doesn't matter. The process is the same for all functions:
1. The current context is "laid away" on top of the stack.
2. The new context is created for the subcall.
3. When the subcall is finished -- the previous context is popped from the stack, and its execution continues.
The context stack as a list where the current execution context is always on-top, and previous remembered contexts are below:
<ul class="function-execution-context-list">
<li>
<span class="function-execution-context">Context: { x: 2, n: 2, at line 1 }</span>
<span class="function-execution-context-call">pow(2, 2)</span>
</li>
<li>
<span class="function-execution-context">Context: { x: 2, n: 3, at line 5 }</span>
<span class="function-execution-context-call">pow(2, 3)</span>
</li>
</ul>
Here, the new current execution context is also made bold for clarity.
Note that the context of the previous call not only includes the variables, but also the place in the code, so when the new call finishes -- it would be easy to resume. We use a word "line" in the illustrations for that, but of course in fact it's more precise.
### pow(2, 1)
The process repeats: a new subcall is made at line `5`, now with arguments `x=2`, `n=1`.
A new execution context is created, the previous one is pushed on top of the stack:
<ul class="function-execution-context-list">
<li>
<span class="function-execution-context">Context: { x: 2, n: 1, at line 1 }</span>
<span class="function-execution-context-call">pow(2, 1)</span>
</li>
<li>
<span class="function-execution-context">Context: { x: 2, n: 2, at line 5 }</span>
<span class="function-execution-context-call">pow(2, 2)</span>
</li>
<li>
<span class="function-execution-context">Context: { x: 2, n: 3, at line 5 }</span>
<span class="function-execution-context-call">pow(2, 3)</span>
</li>
</ul>
The stack has 2 old contexts now.
### The exit
During the execution of `pow(2, 1)`, unlike before, the condition `n == 1` is truthy, so the first branch of `if` works:
```js
function pow(x, n) {
if (n == 1) {
*!*
return x;
*/!*
} else {
return x * pow(x, n - 1);
}
}
```
There are no more nested calls, so the function finishes, returning `2`.
As the function finishes, its execution context is not needed any more, so it's removed from the memory. The previous one is restored off-top of the stack:
<ul class="function-execution-context-list">
<li>
<span class="function-execution-context">Context: { x: 2, n: 2, at line 5 }</span>
<span class="function-execution-context-call">pow(2, 2)</span>
</li>
<li>
<span class="function-execution-context">Context: { x: 2, n: 3, at line 5 }</span>
<span class="function-execution-context-call">pow(2, 3)</span>
</li>
</ul>
The execution of `pow(2, 2)` is resumed. It has the result of the subcall `pow(2, 1)`, so it also can finish the evaluation of `x * pow(x, n-1)`, returning `4`.
Then the previous context is restored:
<ul class="function-execution-context-list">
<li>
<span class="function-execution-context">Context: { x: 2, n: 3, at line 5 }</span>
<span class="function-execution-context-call">pow(2, 3)</span>
</li>
</ul>
When it finishes, we have a result of `pow(2, 3) = 8`.
The recursion depth in this case was: **3**.
As we can see from the illustrations above, recursion depth equals the maximal number of context in the stack.
Note the memory requirements. Contexts take memory. In our case, raising to the power of `n` actually requires the memory for `n` contexts, for all lower values of `n`.
A loop-based algorithm is much memory-saving:
```js
function pow(x, n) {
let result = 1;
for(let i = 0; i < n; i++) {
result *= x;
}
return result
}
```
The iterative `pow` uses a single context changing `i` and `result` in the process.
**Any recursion can be rewritten as a loop. The loop variant is usually more effective.**
...But sometimes the rewrite can be non-trivial, especially when function uses different recursive subcalls, when the branching is more intricate. And the optimization benefits may be unneeded and totally not worth that.
## Итого
Рекурсия -- это когда функция вызывает сама себя, как правило, с другими аргументами.
Существуют много областей применения рекурсивных вызовов. Здесь мы посмотрели на один из них -- решение задачи путём сведения её к более простой (с меньшими аргументами), но также рекурсия используется для работы с "естественно рекурсивными" структурами данных, такими как HTML-документы, для "глубокого" копирования сложных объектов.
Есть и другие применения, с которыми мы встретимся по мере изучения JavaScript.
Здесь мы постарались рассмотреть происходящее достаточно подробно, однако, если пожелаете, допустимо временно забежать вперёд и открыть главу <info:debugging-chrome>, с тем чтобы при помощи отладчика построчно пробежаться по коду и посмотреть стек на каждом шаге. Отладчик даёт к нему доступ.

View file

@ -0,0 +1,26 @@
<style>
.function-execution-context-list {
margin: 0;
padding: 0;
overflow: auto;
}
.function-execution-context {
border: 1px solid black;
font-family: "Consolas", monospace;
padding: 5px 8px;
}
.function-execution-context-call {
color: gray;
padding-left: 20px;
}
.function-execution-context-call::before {
content: '@';
}
.function-execution-context-list li:first-child {
font-weight: bold;
}
</style>

1
1-js/5-deeper/index.md Normal file
View file

@ -0,0 +1 @@
# Deeper

View file

@ -0,0 +1,12 @@
```js
var leader = {
name: "Василий Иванович",
age: 35
};
var leaderStr = JSON.stringify(leader);
leader = JSON.parse(leaderStr);
```

View file

@ -0,0 +1,16 @@
importance: 3
---
# Превратите объект в JSON
Превратите объект `leader` из примера ниже в JSON:
```js
var leader = {
name: "Василий Иванович",
age: 35
};
```
После этого прочитайте получившуюся строку обратно в объект.

View file

@ -0,0 +1,59 @@
# Ответ на первый вопрос
Обычный вызов `JSON.stringify(team)` выдаст ошибку, так как объекты `leader` и `soldier` внутри структуры ссылаются друг на друга.
Формат JSON не предусматривает средств для хранения ссылок.
# Варианты решения
Чтобы превращать такие структуры в JSON, обычно используются два подхода:
1. Добавить в `team` свой код `toJSON`:
```js
team.toJSON = function() {
/* свой код, который может создавать копию объекта без круговых ссылок и передавать управление JSON.stringify */
}
```
При этом, конечно, понадобится и своя функция чтения из JSON, которая будет восстанавливать объект, а затем дополнять его круговыми ссылками.
2. Можно учесть возможную проблему в самой структуре, используя вместо ссылок `id`. Как правило, это несложно, ведь на сервере у данных тоже есть идентификаторы.
Изменённая структура может выглядеть так:
```js
var leader = {
id: 12,
name: "Василий Иванович"
};
var soldier = {
id: 51,
name: "Петька"
};
*!*
// поменяли прямую ссылку на ID
leader.soldierId = 51;
soldier.leaderId = 12;
*/!*
var team = {
12: leader,
51: soldier
};
```
..Но действительно ли это решение будет оптимальным? Использовать структуру стало сложнее, и вряд ли это изменение стоит делать лишь из-за JSON. Вот если есть другие преимущества, тогда можно подумать.
Универсальный вариант подхода, описанного выше -- это использование особой реализации JSON, которая не входит в стандарт и поддерживает расширенный формат для поддержки ссылок.
Она, к примеру, есть во фреймворке Dojo.
При вызове `dojox.json.ref.toJson(team)` будет создано следующее строковое представление:
```js no-beautify
[{"name":"Василий Иванович","soldier":{"name":"Петька","leader":{"$ref":"#0"}}},{"$ref":"#0.soldier"}]
```
Метод разбора такой строки -- также свой: `dojox.json.ref.fromJson`.

View file

@ -0,0 +1,26 @@
importance: 3
---
# Превратите объекты со ссылками в JSON
Превратите объект `team` из примера ниже в JSON:
```js
var leader = {
name: "Василий Иванович"
};
var soldier = {
name: "Петька"
};
// эти объекты ссылаются друг на друга!
leader.soldier = soldier;
soldier.leader = leader;
var team = [leader, soldier];
```
1. Может ли это сделать прямой вызов `JSON.stringify(team)`? Если нет, то почему?
2. Какой подход вы бы предложили для чтения и восстановления таких объектов?

317
archive/5-json/article.md Normal file
View file

@ -0,0 +1,317 @@
# JSON methods, toJSON
The [JSON](http://en.wikipedia.org/wiki/JSON) is mostly used to represent an object as a string.
When we need to send an object over a network -- from the client to server or backwards, this format is used almost all the time.
Javascript provides built-in methods to convert objects into JSON and back, which also allow a few tricks that make them even more useful.
[cut]
## Формат JSON
Данные в формате JSON ([RFC 4627](http://tools.ietf.org/html/rfc4627)) представляют собой:
- JavaScript-объекты `{ ... }` или
- Массивы `[ ... ]` или
- Значения одного из типов:
- строки в двойных кавычках,
- число,
- логическое значение `true`/`false`,
- `null`.
Почти все языки программирования имеют библиотеки для преобразования объектов в формат JSON.
Основные методы для работы с JSON в JavaScript -- это:
- `JSON.parse` -- читает объекты из строки в формате JSON.
- `JSON.stringify` -- превращает объекты в строку в формате JSON, используется, когда нужно из JavaScript передать данные по сети.
## Метод JSON.parse
Вызов `JSON.parse(str)` превратит строку с данными в формате JSON в JavaScript-объект/массив/значение.
Например:
```js run
var numbers = "[0, 1, 2, 3]";
numbers = JSON.parse(numbers);
alert( numbers[1] ); // 1
```
Или так:
```js run
var user = '{ "name": "Вася", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';
user = JSON.parse(user);
alert( user.friends[1] ); // 1
```
Данные могут быть сколь угодно сложными, объекты и массивы могут включать в себя другие объекты и массивы. Главное чтобы они соответствовали формату.
````warn header="JSON-объекты ≠ JavaScript-объекты"
Объекты в формате JSON похожи на обычные JavaScript-объекты, но отличаются от них более строгими требованиями к строкам -- они должны быть именно в двойных кавычках.
В частности, первые два свойства объекта ниже -- некорректны:
```js
{
*!*name*/!*: "Вася", // ошибка: ключ name без кавычек!
"surname": *!*'Петров'*/!*,// ошибка: одинарные кавычки у значения 'Петров'!
"age": 35, // .. а тут всё в порядке.
"isAdmin": false // и тут тоже всё ок
}
```
Кроме того, в формате JSON не поддерживаются комментарии. Он предназначен только для передачи данных.
Есть нестандартное расширение формата JSON, которое называется [JSON5](http://json5.org/) и как раз разрешает ключи без кавычек, комментарии и т.п, как в обычном JavaScript. На данном этапе, это отдельная библиотека.
````
## Умный разбор: JSON.parse(str, reviver)
Метод `JSON.parse` поддерживает и более сложные алгоритмы разбора.
Например, мы получили с сервера объект с данными события `event`.
Он выглядит так:
```js
// title: название события, date: дата события
var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}';
```
...И теперь нужно *восстановить* его, то есть превратить в JavaScript-объект.
Попробуем вызвать для этого `JSON.parse`:
```js run
var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}';
var event = JSON.parse(str);
*!*
alert( event.date.getDate() ); // ошибка!
*/!*
```
...Увы, ошибка!
Дело в том, что значением `event.date` является строка, а отнюдь не объект `Date`. Откуда методу `JSON.parse` знать, что нужно превратить строку именно в дату?
**Для интеллектуального восстановления из строки у `JSON.parse(str, reviver)` есть второй параметр `reviver`, который является функцией `function(key, value)`.**
Если она указана, то в процессе чтения объекта из строки `JSON.parse` передаёт ей по очереди все создаваемые пары ключ-значение и может возвратить либо преобразованное значение, либо `undefined`, если его нужно пропустить.
В данном случае мы можем создать правило, что ключ `date` всегда означает дату:
```js run
// дата в строке - в формате UTC
var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}';
*!*
var event = JSON.parse(str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
*/!*
alert( event.date.getDate() ); // теперь сработает!
```
Кстати, эта возможность работает и для вложенных объектов тоже:
```js run
var schedule = '{ \
"events": [ \
{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}, \
{"title":"День рождения","date":"2015-04-18T12:00:00.000Z"} \
]\
}';
schedule = JSON.parse(schedule, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
*!*
alert( schedule.events[1].date.getDate() ); // сработает!
*/!*
```
## Сериализация, метод JSON.stringify
Метод `JSON.stringify(value, replacer, space)` преобразует ("сериализует") значение в JSON-строку.
Пример использования:
```js run
var event = {
title: "Конференция",
date: "сегодня"
};
var str = JSON.stringify(event);
alert( str ); // {"title":"Конференция","date":"сегодня"}
// Обратное преобразование.
event = JSON.parse(str);
```
**При сериализации объекта вызывается его метод `toJSON`.**
Если такого метода нет -- перечисляются его свойства, кроме функций.
Посмотрим это в примере посложнее:
```js run
var room = {
number: 23,
occupy: function() {
alert( this.number );
}
};
event = {
title: "Конференция",
date: new Date(Date.UTC(2014, 0, 1)),
room: room
};
alert( JSON.stringify(event) );
/*
{
"title":"Конференция",
"date":"2014-01-01T00:00:00.000Z", // (1)
"room": {"number":23} // (2)
}
*/
```
Обратим внимание на два момента:
1. Дата превратилась в строку. Это не случайно: у всех дат есть встроенный метод `toJSON`. Его результат в данном случае -- строка в таймзоне UTC.
2. У объекта `room` нет метода `toJSON`. Поэтому он сериализуется перечислением свойств.
Мы, конечно, могли бы добавить такой метод, тогда в итог попал бы его результат:
```js run
var room = {
number: 23,
*!*
toJSON: function() {
return this.number;
}
*/!*
};
alert( JSON.stringify(room) ); // 23
```
### Исключение свойств
Попытаемся преобразовать в JSON объект, содержащий ссылку на DOM.
Например:
```js run
var user = {
name: "Вася",
age: 25,
window: window
};
*!*
alert( JSON.stringify(user) ); // ошибка!
// TypeError: Converting circular structure to JSON (текст из Chrome)
*/!*
```
Произошла ошибка! В чём же дело, неужели некоторые объекты запрещены? Как видно из текста ошибки -- дело совсем в другом. Глобальный объект `window` -- сложная структура с кучей встроенных свойств и круговыми ссылками, поэтому его преобразовать невозможно. Да и нужно ли?
**Во втором параметре `JSON.stringify(value, replacer)` можно указать массив свойств, которые подлежат сериализации.**
Например:
```js run
var user = {
name: "Вася",
age: 25,
window: window
};
*!*
alert( JSON.stringify(user, ["name", "age"]) );
// {"name":"Вася","age":25}
*/!*
```
Для более сложных ситуаций вторым параметром можно передать функцию `function(key, value)`, которая возвращает сериализованное `value` либо `undefined`, если его не нужно включать в результат:
```js run
var user = {
name: "Вася",
age: 25,
window: window
};
*!*
var str = JSON.stringify(user, function(key, value) {
if (key == 'window') return undefined;
return value;
});
*/!*
alert( str ); // {"name":"Вася","age":25}
```
В примере выше функция пропустит свойство с названием `window`. Для остальных она просто возвращает значение, передавая его стандартному алгоритму. А могла бы и как-то обработать.
```smart header="Функция `replacer` работает рекурсивно"
То есть, если объект содержит вложенные объекты, массивы и т.п., то все они пройдут через `replacer`.
```
### Красивое форматирование
В методе `JSON.stringify(value, replacer, space)` есть ещё третий параметр `space`.
Если он является числом -- то уровни вложенности в JSON оформляются указанным количеством пробелов, если строкой -- вставляется эта строка.
Например:
```js run
var user = {
name: "Вася",
age: 25,
roles: {
isAdmin: false,
isEditor: true
}
};
*!*
var str = JSON.stringify(user, "", 4);
*/!*
alert( str );
/* Результат -- красиво сериализованный объект:
{
"name": "Вася",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
```
## Итого
- JSON -- формат для представления объектов (и не только) в виде строки.
- Методы [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) и [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) позволяют интеллектуально преобразовать объект в строку и обратно.

View file

@ -0,0 +1,326 @@
# Memory Management
Memory management in Javascript is performed automatically and invisibly to us. We create primitives, objects, functions... All that takes memory.
What happens with an object when it's not needed any more? Can we occasionally create problems for memory management and get performance problems because of that? To answer that -- let's get under the hood of the Javascript engine.
[cut]
## 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.
1. There's a base set of reachable values. For instance:
- Local variables and parameters of the current function.
- Variables and parameters for other functions in the current chain of nested calls.
- Global variables.
These variables 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.**
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.
Things are very simple for primitive values. They are copied as a whole on assignment. A single primitive can only be stored in one place, there are no references for them. So if there was a string in a variable, and it was replaced with another string, then the old one can safely be junked.
Objects from the other hand can be referenced from multiple variables. If two variables store a reference to the same object, then even if one of them is overwritten, the object is still accessible through the second one. That's why a special "garbage collector" is needed that watches the references.
The basic garbage collection algorithm is called "mark-and-sweep".
Regularly the following "garbage collection" steps are performed:
- The collector follows all references from roots and remembers the objects.
- All objects except those are removed.
Let's see examples to get the better picture.
## A simple example
Here's the simplest example:
```js
// user has a reference to the object
let user = {
name: "John"
};
// user is overwritten, the reference is lost
user = null;
```
After the variable is overwritten, the object `{name: "John"}` becomes unreachable. There's no way to access it, no references to it. Garbage collector will junk the data and free the memory.
## Two references
Now let's imagine we copied the reference:
```js
// user has a reference to the object
let user = {
name: "John"
};
*!*
let admin = user;
*/!*
user = null;
```
Here we still can access the object via `admin` global variable, so it's in memory. If we overwrite `admin` too, then it can be removed.
## Mutual links
Now a more complex example. The family:
```js
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
```
Function `marry` "marries" two objects by giving them references to each other and returns a new object that contains them both.
The resulting memory structure:
![](family.png)
Here arrows depict references. The `"name"` property is not a reference, it stores a primitive, so it's drawn inside the object.
To activate garbage collection, let's remove two references:
```js
delete family.father;
delete family.mother.husband;
```
Note that if the deletion of any one of them would not lead to anything, because all objects are still reachable.
But if we delete the two, then we can see that the object `"John"` has no incoming references any more:
![](family-no-father.png)
**Outgoing references do not matter. Only incoming ones can make the object reachable.**
The former `family.father` is now unreachable and will be removed from the memory with all its data that also became unaccessible.
After garbage collection:
![](family-no-father-2.png)
## Unreachable island
It is possible that the whole island of interlinked objects becomes unreachable and is removed from the memory.
The source object is the same as above. Then:
```js
family = null;
```
The result:
![](family-no-family.png)
This example demonstrates how important the concept of reachability is.
It is clearly seen that `"John"` and `"Ann"` objects are still linked, both have incoming references. But it'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 became unreachable and will be removed.
## Generational optimization
The simple garbage collection algorithm has a problem.
If there are many objects, then it may take time to walk all paths from the roots and find all unreachables. And that process must be atomic: no new links should appear and no existing ones should be modified until it completes.
Essentially that means that the script execution is paused while the search is in progress. Pauses may be small like few milliseconds for simple scripts or noticeable like `100+ms` for big programs with tons of objects. Such hiccups can be unpleasant and even disruptive for program systems that operate on real-time financial or medical data.
So various optimizations are applied.
One of most widely used in JS engines is so-called "generational" garbage collection.
Objects are split into two sets: "old ones" and "new ones". Each set has its own memory area.
A new object is created in the "new" memory area and, if survived long enough, migrates to the "old" one. The "new" area is usually small and is garbage collected often. The "old" area is big and rarely cleaned up.
In practice, that's very effective, because most objects are created and destroyed almost immediately. For instance when they are local variables of a function.
And few objects survive for a long time, like the object with the current visitor data.
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).
## Замыкания
Объекты переменных, о которых шла речь ранее, в главе про замыкания, также подвержены сборке мусора. Они следуют тем же правилам, что и обычные объекты.
Объект переменных внешней функции существует в памяти до тех пор, пока существует хоть одна внутренняя функция, ссылающаяся на него через свойство `[[Scope]]`.
Например:
- Обычно объект переменных удаляется по завершении работы функции. Даже если в нём есть объявление внутренней функции:
```js
function f() {
var value = 123;
function g() {} // g видна только изнутри
}
f();
```
В коде выше `value` и `g` являются свойствами объекта переменных. Во время выполнения `f()` её объект переменных находится в текущем стеке выполнения, поэтому жив. По окончанию, он станет недостижимым и будет убран из памяти вместе с остальными локальными переменными.
- ...А вот в этом случае лексическое окружение, включая переменную `value`, будет сохранено:
```js
function f() {
var value = 123;
function g() {}
*!*
return g;
*/!*
}
var g = f(); // функция g будет жить и сохранит ссылку на объект переменных
```
В скрытом свойстве `g.[[Scope]]` находится ссылка на объект переменных, в котором была создана `g`. Поэтому этот объект переменных останется в памяти, а в нём -- и `value`.
- Если `f()` будет вызываться много раз, а полученные функции будут сохраняться, например, складываться в массив, то будут сохраняться и объекты `LexicalEnvironment` с соответствующими значениями `value`:
```js
function f() {
var value = Math.random();
return function() {};
}
// 3 функции, каждая ссылается на свой объект переменных,
// каждый со своим значением value
var arr = [f(), f(), f()];
```
- Объект `LexicalEnvironment` живёт ровно до тех пор, пока на него существуют ссылки. В коде ниже после удаления ссылки на `g` умирает:
```js
function f() {
var value = 123;
function g() {}
return g;
}
var g = f(); // функция g жива
// а значит в памяти остается соответствующий объект переменных f()
g = null; // ..а вот теперь память будет очищена
```
### Оптимизация в V8 и её последствия
Современные JS-движки делают оптимизации замыканий по памяти. Они анализируют использование переменных и в случае, когда переменная из замыкания абсолютно точно не используется, удаляют её.
В коде выше переменная `value` никак не используется. Поэтому она будет удалена из памяти.
**Важный побочный эффект в V8 (Chrome, Opera) состоит в том, что удалённая переменная станет недоступна и при отладке!**
Попробуйте запустить пример ниже с открытой консолью Chrome. Когда он остановится, в консоли наберите `alert(value)`.
```js run
function f() {
var value = Math.random();
function g() {
debugger; // выполните в консоли alert( value ); Нет такой переменной!
}
return g;
}
var g = f();
g();
```
Как вы могли увидеть -- нет такой переменной! Недоступна она изнутри `g`. Интерпретатор решил, что она нам не понадобится и удалил.
Это может привести к забавным казусам при отладке, вплоть до того что вместо этой переменной будет другая, внешняя:
```js run
var value = "Сюрприз";
function f() {
var value = "самое близкое значение";
function g() {
debugger; // выполните в консоли alert( value ); Сюрприз!
}
return g;
}
var g = f();
g();
```
```warn header="Ещё увидимся"
Об этой особенности важно знать. Если вы отлаживаете под Chrome/Opera, то наверняка рано или поздно с ней встретитесь!
Это не глюк отладчика, а особенность работы V8, которая, возможно, будет когда-нибудь изменена. Вы всегда сможете проверить, не изменилось ли чего, запустив примеры на этой странице.
```
## Влияние управления памятью на скорость
На создание новых объектов и их удаление тратится время. Это важно иметь в виду в случае, когда важна производительность.
В качестве примера рассмотрим рекурсию. При вложенных вызовах каждый раз создаётся новый объект с переменными и помещается в стек. Потом память из-под него нужно очистить. Поэтому рекурсивный код будет всегда медленнее использующего цикл, но насколько?
Пример ниже тестирует сложение чисел до данного через рекурсию по сравнению с обычным циклом:
```js run
function sumTo(n) { // обычный цикл 1+2+...+n
var result = 0;
for (var i = 1; i <= n; i++) {
result += i;
}
return result;
}
function sumToRec(n) { // рекурсия sumToRec(n) = n+SumToRec(n-1)
return n == 1 ? 1 : n + sumToRec(n - 1);
}
var timeLoop = performance.now();
for (var i = 1; i < 1000; i++) sumTo(1000); // цикл
timeLoop = performance.now() - timeLoop;
var timeRecursion = performance.now();
for (var i = 1; i < 1000; i++) sumToRec(1000); // рекурсия
timeRecursion = performance.now() - timeRecursion;
alert( "Разница в " + (timeRecursion / timeLoop) + " раз" );
```
Различие в скорости на таком примере может составлять, в зависимости от интерпретатора, 2-10 раз.
Вообще, этот пример -- не показателен. Ещё раз обращаю ваше внимание на то, что такие искусственные "микротесты" часто врут. Правильно их делать -- отдельная наука, которая выходит за рамки этой главы. Но и на практике ускорение в 2-10 раз оптимизацией по количеству объектов (и вообще, любых значений) -- отнюдь не миф, а вполне достижимо.
В реальной жизни в большинстве ситуаций такая оптимизация несущественна, просто потому что "JavaScript и так достаточно быстр". Но она может быть эффективной для "узких мест" кода.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

51
archive/backticks.md Normal file
View file

@ -0,0 +1,51 @@
Backticks allow to specify a "template function" before the first backtick.
The syntax is:
```js
let str = func`my string`;
```
The function `func` is automatically called and receives the array of string pieces split by `${...}` embeds and the list of embedded `${...}` values.
It's better to see the example:
```js run
function love(s, v1, v2) {
alert( s[0] ); // Hello:
alert( s[1] ); // and
alert( s[2] ); // !
alert( v1 ); // Ilya
alert( v2 ); // Julia
alert( s.raw[0] ); // Hello:\n
return v1 + ' ♥ ' + v2;
}
let mom = "Julia";
let dad = "Ilya";
// s[0] | v1 | s[1] | v2 | s[2]
let str = love`Hello:\n ${mom} and ${dad}!`;
alert(str); // 'Julia ♥ Ilya'
```
The string inside <code>love&#96;string&#96;</code> is split into pieces by embeds. The result is passed to `love`:
- The first parameter `s` stores the pieces, here `s = ["Hello:\n ", " and ", "!"]`.
- The special proeprty `s.raw` contains unparsed values, the special characters are not processes.
- Then follow the values of embeds: `v1 = mom`, `v2 = data`.
Templating functions allow to
- The first parameter `strings` is a
In the example above, `love` is the name for the function. It is called with an array

22
archive/commend.md Normal file
View file

@ -0,0 +1,22 @@
### Don't hesitate to comment
Please, don't hesitate to comment.
Comments increase the overall code footprint, but that's not a problem at all, because there are many tools which minify the code before publishing to production server. They remove comments, so they do not appear in the working scripts.
The code clarity is what we should hunt for. And comments can really help to make it easier to understand.
There are various types of comments, answering different questions:
- What the code does?
- Why the code is written like that?
- Which counter-intuitive or implicit connections it has with other parts of the program?
Further in the tutorial we'll make more notes about how to write the code better, easier to read and maintain. We'll also talk more about comments.
```smart header="The good code is inherently readable and self-commenting"
Please note that the first type of comments ("what the code does") should be used to describe a "high-level" action, like the overall architecture, a function or a chunk of code. It's purpose is to give an overview, so a reader doesn't need to delve into the code and figure out.
Novice programmers sometimes tend to elaborate too much. Please don't. The good code is inherently readable. No need to describe what few lines do. Unless it's something hard to grasp, and *then* it's worth to consider rewriting the code at the first place rather than commenting it.
```

37
archive/descriptors.md Normal file
View file

@ -0,0 +1,37 @@
## Property descriptors
An object property is actually a more complex and tunable thing than just a "key-value" mapping.
There are two kinds of properties.
The first is *data properties*.
They assiciate a key with the attributes:
- **`value`** -- the value of the property.
- **`writable`** -- if `true`, can be changed, otherwise it's read-only.
- **`enumerable`** -- if `true`, then listed in loops, otherwise not listed.
- **`configurable`** -- if `true`, the property can be deleted and these attributes can be modified, otherwise not.
All properties that we've seen yet were data properties.
By default when a property is created, all attributes are `true`.
We can get the the information about an existing property using [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor):
```js run
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( descriptor.value ); // John
alert( descriptor.writable ); // true
alert( descriptor.enumerable ); // true
alert( descriptor.configurable ); // true
```

Binary file not shown.