This commit is contained in:
Ilya Kantor 2017-02-19 01:41:36 +03:00
parent d7d25f4d8b
commit 20784e7f26
48 changed files with 302 additions and 397 deletions

View file

@ -1,8 +1,12 @@
Этот тест демонстрирует один из соблазнов, которые ожидают начинающего автора тестов.
The test demonstrates one of temptations a developer meets when writing tests.
Вместо того, чтобы написать три различных теста, он изложил их в виде одного потока вычислений, с несколькими `assert`.
What we have here is actually 3 tests, but layed out as a single function with 3 asserts.
Иногда так написать легче и проще, однако при ошибке в тесте гораздо менее очевидно, что же пошло не так.
Sometimes it's easier to write this way, but if an error occurs, it's much less obvious what went wrong.
If an error happens inside a complex execution flow, then we'll have to figure out what was the data at that point.
TODO
Если в сложном тесте произошла ошибка где-то посередине потока вычислений, то придётся выяснять, какие конкретно были входные и выходные данные на этот момент, то есть по сути -- отлаживать код самого теста.

View file

@ -2,23 +2,23 @@ importance: 5
---
# Что не так в тесте?
# What's wrong in the test?
Что не так в этом тесте функции `pow`?
What's wrong in the test of `pow` below?
```js
it("Возводит x в степень n", function() {
var x = 5;
it("Raises x to the power n", function() {
let x = 5;
var result = x;
let result = x;
assert.equal(pow(x, 1), result);
var result *= x;
result *= x;
assert.equal(pow(x, 2), result);
var result *= x;
result *= x;
assert.equal(pow(x, 3), result);
});
```
P.S. Синтаксически он верен и работает, но спроектирован неправильно.
P.S. Syntactically it's correct and passes.

View file

@ -1,18 +1,14 @@
Да, возможны.
Yes, it's possible.
Они должны возвращать одинаковый объект. При этом если функция возвращает объект, то `this` не используется.
If a function returns an object then `new` returns it instead of `this`.
Например, они могут вернуть один и тот же объект `obj`, определённый снаружи:
So thay can, for instance, return the same externally defined object `obj`:
```js run no-beautify
var obj = {};
let obj = {};
function A() { return obj; }
function B() { return obj; }
var a = new A;
var b = new B;
alert( a == b ); // true
alert( new A() == new B() ); // true
```

View file

@ -2,9 +2,9 @@ importance: 2
---
# Две функции один объект
# Two functions -- one object
Возможны ли такие функции `A` и `B` в примере ниже, что соответствующие объекты `a,b` равны (см. код ниже)?
Is it possible to create functions `A` and `B` such as `new A()==new B()`?
```js no-beautify
function A() { ... }
@ -16,4 +16,4 @@ var b = new B;
alert( a == b ); // true
```
Если да -- приведите пример кода с такими функциями.
If it is, then provide an example of their code.

View file

@ -1,25 +1,25 @@
describe("calculator", function() {
let calculator;
before(function() {
sinon.stub(window, "prompt")
prompt.onCall(0).returns("2");
prompt.onCall(1).returns("3");
describe("calculator", function() {
var calculator;
before(function() {
calculator = new Calculator();
calculator.read();
});
it("при вводе 2 и 3 сумма равна 5", function() {
it("when 2 and 3 are entered, the sum is 5", function() {
assert.equal(calculator.sum(), 5);
});
it("при вводе 2 и 3 произведение равно 6", function() {
it("when 2 and 3 are entered, the product is 6", function() {
assert.equal(calculator.mul(), 6);
});
});
after(function() {
prompt.restore();
});
});

View file

@ -17,10 +17,9 @@ function Calculator() {
};
}
var calculator = new Calculator();
let calculator = new Calculator();
calculator.read();
alert( "Сумма=" + calculator.sum() );
alert( "Произведение=" + calculator.mul() );
alert( "Sum=" + calculator.sum() );
alert( "Mul=" + calculator.mul() );
```

View file

@ -2,23 +2,22 @@ importance: 5
---
# Создать Calculator при помощи конструктора
# Create new Calculator
Напишите *функцию-конструктор* `Calculator`, которая создает объект с тремя методами:
Create a constructor function `Calculator` that creates objects with 3 methods:
- Метод `read()` запрашивает два значения при помощи `prompt` и запоминает их в свойствах объекта.
- Метод `sum()` возвращает сумму запомненных свойств.
- Метод `mul()` возвращает произведение запомненных свойств.
- `read()` asks for two values using `prompt` and remembers them in object properties.
- `sum()` returns the sum of these properties.
- `mul()` returns the multiplication product of these properties.
Пример использования:
For instance:
```js
var calculator = new Calculator();
let calculator = new Calculator();
calculator.read();
alert( "Сумма=" + calculator.sum() );
alert( "Произведение=" + calculator.mul() );
alert( "Sum=" + calculator.sum() );
alert( "Mul=" + calculator.mul() );
```
[demo]

View file

@ -2,7 +2,7 @@ function Accumulator(startingValue) {
this.value = startingValue;
this.read = function() {
this.value += +prompt('Сколько добавлять будем?', 0);
this.value += +prompt('How much to add?', 0);
};
}

View file

@ -1,8 +1,4 @@
describe("Accumulator(1)", function() {
var accumulator;
before(function() {
accumulator = new Accumulator(1);
});
describe("Accumulator", function() {
beforeEach(function() {
sinon.stub(window, "prompt")
@ -12,26 +8,23 @@ describe("Accumulator(1)", function() {
prompt.restore();
});
it("начальное значение 1", function() {
it("initial value is the argument of the constructor", function() {
let accumulator = new Accumulator(1);
assert.equal(accumulator.value, 1);
});
it("после ввода 0 значение 1", function() {
it("after reading 0, the value is 1", function() {
let accumulator = new Accumulator(1);
prompt.returns("0");
accumulator.read();
assert.equal(accumulator.value, 1);
});
it("после ввода 1 значение 2", function() {
it("after reading 1, the value is 2", function() {
let accumulator = new Accumulator(1);
prompt.returns("1");
accumulator.read();
assert.equal(accumulator.value, 2);
});
it("после ввода 2 значение 4", function() {
prompt.returns("2");
accumulator.read();
assert.equal(accumulator.value, 4);
});
});

View file

@ -5,14 +5,13 @@ function Accumulator(startingValue) {
this.value = startingValue;
this.read = function() {
this.value += +prompt('Сколько добавлять будем?', 0);
this.value += +prompt('How much to add?', 0);
};
}
var accumulator = new Accumulator(1);
let accumulator = new Accumulator(1);
accumulator.read();
accumulator.read();
alert(accumulator.value);
```

View file

@ -2,26 +2,24 @@ importance: 5
---
# Создать Accumulator при помощи конструктора
# Create new Accumulator
Напишите *функцию-конструктор* `Accumulator(startingValue)`.
Объекты, которые она создает, должны хранить текущую сумму и прибавлять к ней то, что вводит посетитель.
Create a constructor function `Accumulator(startingValue)`.
Более формально, объект должен:
Object that it creates should:
- Хранить текущее значение в своём свойстве `value`. Начальное значение свойства `value` ставится конструктором равным `startingValue`.
- Метод `read()` вызывает `prompt`, принимает число и прибавляет его к свойству `value`.
- Store the "current value" in the property `value`. The starting value is set to the argument of the constructor `startingValue`.
- The `read()` method should use `prompt` to read a new number and add it to `value`.
Таким образом, свойство `value` является текущей суммой всего, что ввел посетитель при вызовах метода `read()`, с учетом начального значения `startingValue`.
In other words, the `value` property is the sum of all user-entered values with the initial value `startingValue`.
Ниже вы можете посмотреть работу кода:
Here's the demo of the code:
```js
var accumulator = new Accumulator(1); // начальное значение 1
accumulator.read(); // прибавит ввод prompt к текущему значению
accumulator.read(); // прибавит ввод prompt к текущему значению
alert( accumulator.value ); // выведет текущее значение
let accumulator = new Accumulator(1); // initial value 1
accumulator.read(); // adds the user-entered value
accumulator.read(); // adds the user-entered value
alert(accumulator.value); // shows the sum of these values
```
[demo]

View file

@ -1,17 +1,13 @@
function Calculator() {
var methods = {
"-": function(a, b) {
return a - b;
},
"+": function(a, b) {
return a + b;
}
let methods = {
"-": (a, b) => a - b,
"+": (a, b) => a + b
};
this.calculate = function(str) {
var split = str.split(' '),
let split = str.split(' '),
a = +split[0],
op = split[1],
b = +split[2]

View file

@ -1,4 +1,6 @@
var calculator;
describe("Calculator", function() {
let calculator;
before(function() {
calculator = new Calculator;
});
@ -11,16 +13,13 @@ it("calculate(34 - 12) = 22", function() {
assert.equal(calculator.calculate("34 - 12"), 22);
});
it("добавили умножение: calculate(2 * 3) = 6", function() {
calculator.addMethod("*", function(a, b) {
return a * b;
});
it("add multiplication: calculate(2 * 3) = 6", function() {
calculator.addMethod("*", (a, b) => a * b);
assert.equal(calculator.calculate("2 * 3"), 6);
});
it("добавили возведение в степень: calculate(2 ** 3) = 8", function() {
calculator.addMethod("**", function(a, b) {
return Math.pow(a, b);
});
it("add power: calculate(2 ** 3) = 8", function() {
calculator.addMethod("**", (a, b) => a ** b);
assert.equal(calculator.calculate("2 ** 3"), 8);
});
});

View file

@ -1,52 +1,3 @@
```js run
function Calculator() {
var methods = {
"-": function(a, b) {
return a - b;
},
"+": function(a, b) {
return a + b;
}
};
this.calculate = function(str) {
var split = str.split(' '),
a = +split[0],
op = split[1],
b = +split[2]
if (!methods[op] || isNaN(a) || isNaN(b)) {
return NaN;
}
return methods[op](+a, +b);
}
this.addMethod = function(name, func) {
methods[name] = func;
};
}
var calc = new Calculator;
calc.addMethod("*", function(a, b) {
return a * b;
});
calc.addMethod("/", function(a, b) {
return a / b;
});
calc.addMethod("**", function(a, b) {
return Math.pow(a, b);
});
var result = calc.calculate("2 ** 3");
alert( result ); // 8
```
- Обратите внимание на хранение методов. Они просто добавляются к внутреннему объекту.
- Все проверки и преобразование к числу производятся в методе `calculate`. В дальнейшем он может быть расширен для поддержки более сложных выражений.
- Please note how methods are stored. They are simply added to the internal object.
- All tests and numeric conversions are done in the `calculate` method. In future it may be extended to support more complex expressions.

View file

@ -2,41 +2,35 @@ importance: 5
---
# Создайте калькулятор
# Create an extendable calculator
Напишите конструктор `Calculator`, который создаёт расширяемые объекты-калькуляторы.
Create a constructor function `Calculator` that creates "extendable" calculator objects.
Эта задача состоит из двух частей, которые можно решать одна за другой.
The task consists of two parts.
1. Первый шаг задачи: вызов `calculate(str)` принимает строку, например "1 + 2", с жёстко заданным форматом "ЧИСЛО операция ЧИСЛО" (по одному пробелу вокруг операции), и возвращает результат. Понимает плюс `+` и минус `-`.
1. First, implement the method `calculate(str)` that takes a string like `"1 + 2"` in the format "NUMBER operator NUMBER" (space-delimited) and returns the result. Should understand plus `+` and minus `-`.
Пример использования:
Usage example:
```js
var calc = new Calculator;
let calc = new Calculator;
alert( calc.calculate("3 + 7") ); // 10
```
2. Второй шаг -- добавить калькулятору метод `addMethod(name, func)`, который учит калькулятор новой операции. Он получает имя операции `name` и функцию от двух аргументов `func(a,b)`, которая должна её реализовывать.
2. Then add the method `addOperator(name, func)` that teaches the calculator a new operation. It takes the operator `name` and the two-argument function `func(a,b)` that implements it.
Например, добавим операции умножить `*`, поделить `/` и возвести в степень `**`:
For instance, let's add the multiplication `*`, division `/` and power `**`:
```js
var powerCalc = new Calculator;
powerCalc.addMethod("*", function(a, b) {
return a * b;
});
powerCalc.addMethod("/", function(a, b) {
return a / b;
});
powerCalc.addMethod("**", function(a, b) {
return Math.pow(a, b);
});
let powerCalc = new Calculator;
powerCalc.addMethod("*", (a, b) => a * b);
powerCalc.addMethod("/", (a, b) => a / b);
powerCalc.addMethod("**", (a, b) => a ** b);
var result = powerCalc.calculate("2 ** 3");
let result = powerCalc.calculate("2 ** 3");
alert( result ); // 8
```
- Поддержка скобок и сложных математических выражений в этой задаче не требуется.
- Числа и операции могут состоять из нескольких символов. Между ними ровно один пробел.
- Предусмотрите обработку ошибок. Какая она должна быть - решите сами.
- No brackets or complex expressions in this task.
- The numbers and the operator are delimited with exactly one space.
- There may be error handling if you'd like to add it.

View file

@ -1,10 +1,11 @@
function unique(arr) {
var obj = {};
let result = [];
for (var i = 0; i < arr.length; i++) {
var str = arr[i];
obj[str] = true; // запомнить строку в виде свойства объекта
for (let str of arr) {
if (!result.includes(str)) {
result.push(str);
}
}
return Object.keys(obj); // или собрать ключи перебором для IE8-
return result;
}

View file

@ -1,15 +1,15 @@
describe("unique", function() {
it("убирает неуникальные элементы из массива", function() {
var strings = ["кришна", "кришна", "харе", "харе",
"харе", "харе", "кришна", "кришна", "8-()"
it("removes non-unique elements", function() {
let strings = ["Hare", "Krishna", "Hare", "Krishna",
"Krishna", "Krishna", "Hare", "Hare", ":-O"
];
assert.deepEqual(unique(strings), ["кришна", "харе", "8-()"]);
assert.deepEqual(unique(strings), ["Hare", "Krishna", ":-O"]);
});
it("не изменяет исходный массив", function() {
var strings = ["кришна", "кришна", "харе", "харе"];
it("does not change the source array", function() {
let strings = ["Krishna", "Krishna", "Hare", "Hare"];
unique(strings);
assert.deepEqual(strings, ["кришна", "кришна", "харе", "харе"]);
assert.deepEqual(strings, ["Krishna", "Krishna", "Hare", "Hare"]);
});
});

View file

@ -1,78 +1,39 @@
# Решение перебором (медленное)
Пройдём по массиву вложенным циклом.
Для каждого элемента мы будем искать, был ли такой уже. Если был -- игнорировать:
Let's walk the array items:
- For each item we'll check if the resulting array already has that item.
- If it is so, then ignore, otherwise add to results.
```js run
function unique(arr) {
var result = [];
let result = [];
nextInput:
for (var i = 0; i < arr.length; i++) {
var str = arr[i]; // для каждого элемента
for (var j = 0; j < result.length; j++) { // ищем, был ли он уже?
if (result[j] == str) continue nextInput; // если да, то следующий
}
for (let str of arr) {
if (!result.includes(str) {
result.push(str);
}
}
return result;
}
var strings = ["кришна", "кришна", "харе", "харе",
"харе", "харе", "кришна", "кришна", "8-()"
let strings = ["Hare", "Krishna", "Hare", "Krishna",
"Krishna", "Krishna", "Hare", "Hare", ":-O"
];
alert( unique(strings) ); // кришна, харе, 8-()
alert( unique(strings) ); // Hare, Krishna, :-O
```
Давайте посмотрим, насколько быстро он будет работать.
The code works, but there's a potential performance problem in it.
Предположим, в массиве `100` элементов. Если все они одинаковые, то `result` будет состоять из одного элемента и вложенный цикл будет выполняться сразу. В этом случае всё хорошо.
The method `result.includes(str)` internally walks the array `result` and compares each element against `str` to find the match.
А если все, или почти все элементы разные?
So if there are `100` elements in `result` and no one matches `str`, then it will walk the whole `result` and do exactly `100` comparisons. And if `result` is large, like `10000`, then there would be `10000` comparisons.
В этом случае для каждого элемента понадобится обойти весь текущий массив результатов, после чего -- добавить в этот массив.
That's not a problem by itself, because Javascript engines are very fast, so walk `10000` array is a matter of microseconds.
1. Для первого элемента -- это обойдётся в `0` операций доступа к элементам `result` (он пока пустой).
2. Для второго элемента -- это обойдётся в `1` операцию доступа к элементам `result`.
3. Для третьего элемента -- это обойдётся в `2` операции доступа к элементам `result`.
4. ...Для n-го элемента -- это обойдётся в `n-1` операций доступа к элементам `result`.
But we do such test for each element of `arr`, in the `for` loop.
Всего <code>0 + 1 + 2 + ... + n-1 = (n-1)*n/2 = n<sup>2</sup>/2 - n/2</code> (как сумма арифметической прогрессии), то есть количество операций растёт примерно как квадрат от `n`.
So if `arr.length` is `10000` we'll have something like `10000*10000` = 100 millions of comparisons. That's a lot.
Это очень быстрый рост. Для `100` элементов -- `4950` операций, для `1000` -- `499500` (по формуле выше).
So the solution is only good for small arrays.
Поэтому такое решение подойдёт только для небольших массивов. Вместо вложенного `for` можно использовать и `arr.indexOf`, ситуация от этого не поменяется, так как `indexOf` тоже ищет перебором.
# Решение с объектом (быстрое)
Наилучшая техника для выбора уникальных строк -- использование вспомогательного объекта `obj`. Ведь название свойства в объекте, с одной стороны -- строка, а с другой -- всегда уникально. Повторная запись в свойство с тем же именем перезапишет его.
Например, если `"харе"` попало в объект один раз (`obj["харе"] = true`), то второе такое же присваивание ничего не изменит.
Решение ниже создаёт объект `obj = {}` и записывает в него все строки как имена свойств. А затем собирает свойства из объекта в массив через `for..in`. Дубликатов уже не будет.
```js run
function unique(arr) {
var obj = {};
for (var i = 0; i < arr.length; i++) {
var str = arr[i];
*!*
obj[str] = true; // запомнить строку в виде свойства объекта
*/!*
}
return Object.keys(obj); // или собрать ключи перебором для IE8-
}
var strings = ["кришна", "кришна", "харе", "харе",
"харе", "харе", "кришна", "кришна", "8-()"
];
alert( unique(strings) ); // кришна, харе, 8-()
```
Так что можно положить все значения как ключи в объект, а потом достать.
Further in the chapter <info:map-set-weakmap-weakset> we'll see how to optimize it.

View file

@ -1,24 +1,23 @@
importance: 3
importance: 4
---
# Оставить уникальные элементы массива
# Filter unique array members
Пусть `arr` -- массив строк.
Let `arr` be an array.
Напишите функцию `unique(arr)`, которая возвращает массив, содержащий только уникальные элементы `arr`.
Create a function `unique(arr)` that should return an array with unique items of `arr`.
Например:
For instance:
```js
function unique(arr) {
/* ваш код */
/* your code */
}
var strings = ["кришна", "кришна", "харе", "харе",
"харе", "харе", "кришна", "кришна", "8-()"
let strings = ["Hare", "Krishna", "Hare", "Krishna",
"Krishna", "Krishna", "Hare", "Hare", ":-O"
];
alert( unique(strings) ); // кришна, харе, 8-()
alert( unique(strings) ); // Hare, Krishna, :-O
```

View file

@ -535,7 +535,7 @@ The calculation flow:
Or in the form of a table, where each row represents is a function call on the next array element:
| |`sum`|`current`|результат|
| |`sum`|`current`|`result`|
|---|-----|---------|---------|
|the first call|`0`|`1`|`1`|
|the second call|`1`|`2`|`3`|

View file

@ -0,0 +1,3 @@
function unique(arr) {
return Array.from(new Set(arr));
}

View file

@ -0,0 +1,15 @@
describe("unique", function() {
it("removes non-unique elements", function() {
let strings = ["Hare", "Krishna", "Hare", "Krishna",
"Krishna", "Krishna", "Hare", "Hare", ":-O"
];
assert.deepEqual(unique(strings), ["Hare", "Krishna", ":-O"]);
});
it("does not change the source array", function() {
let strings = ["Krishna", "Krishna", "Hare", "Hare"];
unique(strings);
assert.deepEqual(strings, ["Krishna", "Krishna", "Hare", "Hare"]);
});
});

View file

@ -0,0 +1,27 @@
importance: 5
---
# Filter unique array members
Let `arr` be an array.
Create a function `unique(arr)` that should return an array with unique items of `arr`.
For instance:
```js
function unique(arr) {
/* your code */
}
let values = ["Hare", "Krishna", "Hare", "Krishna",
"Krishna", "Krishna", "Hare", "Hare", ":-O"
];
alert( unique(values) ); // Hare, Krishna, :-O
```
P.S. Here strings are used, but can be values of any type.
P.P.S. Use `Set` to store unique values.

View file

@ -251,7 +251,7 @@ let options = {
let {width=100, height=200, title} = options;
*/!*
alert(title); // Меню
alert(title); // Menu
alert(width); // 100
alert(height); // 200
```

View file

@ -1,4 +1,6 @@
let arr = [1, 2, 3, 4, 5, 6, 7];
function inBetween(a, b) {
// ...your code...
}

View file

@ -19,4 +19,3 @@ describe("inBetween", function() {
assert.isFalse(filter(0));
});
});

View file

@ -1,79 +1,26 @@
# Функция фильтрации
# Filter inBetween
```js run
function filter(arr, func) {
var result = [];
for (var i = 0; i < arr.length; i++) {
var val = arr[i];
if (func(val)) {
result.push(val);
}
}
return result;
}
var arr = [1, 2, 3, 4, 5, 6, 7];
alert(filter(arr, function(a) {
return a % 2 == 0;
})); // 2, 4, 6
```
# Фильтр inBetween
```js run
function filter(arr, func) {
var result = [];
for (var i = 0; i < arr.length; i++) {
var val = arr[i];
if (func(val)) {
result.push(val);
}
}
return result;
}
*!*
function inBetween(a, b) {
return function(x) {
return x >= a && x <= b;
};
}
*/!*
var arr = [1, 2, 3, 4, 5, 6, 7];
alert( filter(arr, inBetween(3, 6)) ); // 3,4,5,6
let arr = [1, 2, 3, 4, 5, 6, 7];
alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6
```
# Фильтр inArray
# Filter inArray
```js run
function filter(arr, func) {
var result = [];
for (var i = 0; i < arr.length; i++) {
var val = arr[i];
if (func(val)) {
result.push(val);
}
}
return result;
}
*!*
function inArray(arr) {
return function(x) {
return arr.indexOf(x) != -1;
return arr.includes(x);
};
}
*/!*
var arr = [1, 2, 3, 4, 5, 6, 7];
alert( filter(arr, inArray([1, 2, 10])) ); // 1,2
let arr = [1, 2, 3, 4, 5, 6, 7];
alert( arr.filter(inArray([1, 2, 10])) ); // 1,2
```

View file

@ -20,7 +20,7 @@ The usual way to do that would be:
// by name (Ann, John, Pete)
users.sort((a, b) => a.name > b.name ? 1 : -1);
// по age (Pete, Ann, John)
// by age (Pete, Ann, John)
users.sort((a, b) => a.age > b.age ? 1 : -1);
```

View file

@ -1,17 +1,11 @@
function makeArmy() {
var shooters = [];
let shooters = [];
for (var i = 0; i < 10; i++) {
var shooter = (function(x) {
return function() {
alert(x);
for(let i = 0; i < 10; i++) {
let shooter = function() { // shooter function
alert( i ); // should show its number
};
})(i);
shooters.push(shooter);
}

View file

@ -1,13 +1,22 @@
function makeArmy() {
let shooters = [];
var shooters = [];
for (var i = 0; i < 10; i++) {
var shooter = function() { // функция-стрелок
alert(i); // выводит свой номер
let i = 0;
while (i < 10) {
let shooter = function() { // shooter function
alert( i ); // should show its number
};
shooters.push(shooter);
i++;
}
return shooters;
}
/*
let army = makeArmy();
army[0](); // the shooter number 0 shows 10
army[5](); // and number 5 also outputs 10...
// ... all shooters show 10 instead of their 0, 1, 2, 3...
*/

View file

@ -1,16 +1,19 @@
var army;
describe("army", function() {
let army;
before(function() {
army = makeArmy();
window.alert = sinon.stub(window, "alert");
});
it("army[0] выводит 0", function() {
it("army[0] shows 0", function() {
army[0]();
assert(alert.calledWith(0));
});
it("army[5] функция выводит 5", function() {
it("army[5] shows 5", function() {
army[5]();
assert(alert.calledWith(5));
});
@ -18,3 +21,5 @@ it("army[5] функция выводит 5", function() {
after(function() {
window.alert.restore();
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Before After
Before After

View file

@ -1,12 +1,12 @@
The function `makeArmy` does the following:
Let's examine what's done inside `makeArmy`, and the solution will become obvious.
1. Creates an empty array `shooters`:
1. It creates an empty array `shooters`:
```js
let shooters = [];
```
2. Fills it in the loop via `shooters.push`.
2. Fills it in the loop via `shooters.push(function...)`.
Every element is a function, so the resulting array looks like this:
@ -27,15 +27,31 @@ The function `makeArmy` does the following:
3. The array is returned from the function.
The call to `army[5]()` -- is getting the element `army[5]` from the array (it will be a function) and -- the immediate call of it.
Then, later, the call to `army[5]()` will get the element `army[5]` from the array (it will be a function) and call it.
Now why all shooters show the same.
Now why all such functions show the same?
There's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment.
That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment.
What will be the value of `i`?
It lives in the lexical environment associated with `makeArmy()` run. At the moment of the call, `makeArmy` already finished its job. The last value of `i` in the `while` loop was `i=10`.
If we look at the source:
```js
function makeArmy() {
...
let i = 0;
while (i < 10) {
let shooter = function() { // shooter function
alert( i ); // should show its number
};
...
}
...
}
```
...We can see that it lives in the lexical environment associated with the current `makeArmy()` run. But when `army[5]()` is called, `makeArmy` has already finished its job, and `i` has the last value: `10` (the end of `while`).
As a result, all `shooter` functions get from the outer lexical envrironment the same, last value `i=10`.
@ -70,7 +86,9 @@ So, the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical
![](lexenv-makearmy.png)
Here we rewrote `while` into `for`. But it is also possible to keep the existing `while` structure:
Here we rewrote `while` into `for`.
Another trick could be possible, let's see it for better understanding of the subject:
```js run
@ -98,7 +116,6 @@ army[0](); // 0
army[5](); // 5
```
Look at the trick. The `while` loop, just like `for`, makes a new Lexical Environment for each run. So we must make sure that it gets the right value for a `shooter` will access it.
The `while` loop, just like `for`, makes a new Lexical Environment for each run. So here we make sure that it gets the right value for a `shooter`.
We copy `let j = i`. This makes a loop body local `j` and copies the value of `i` to it. Primitives are copied "by value", so we actually get a complete independent copy of `i`, belonging to the current loop iteration.

View file

@ -37,7 +37,7 @@ For instance, this code calls `sayHi()` after one second:
```js run
function sayHi() {
alert( 'Привет' );
alert('Hello');
}
*!*
@ -353,7 +353,7 @@ For server-side Javascript, that limitation does not exist. Also, there are othe
## Summary
- Methods `setInterval(func, delay, ...args)` и `setTimeout(func, delay, ...args)` allow to run the `func` regularly/once after `delay` milliseconds.
- Methods `setInterval(func, delay, ...args)` and `setTimeout(func, delay, ...args)` allow to run the `func` regularly/once after `delay` milliseconds.
- To cancel execution, we should `clearInterval/clearTimeout` on the value returned by `setInterval/setTimeout`.
- Nested `setTimeout` calls give more flexible control over the execution than `setInterval`. They also can guarantee the minimal time *between* the execution.
- Zero-timeout is sometimes used to schedule the call "as soon as possible".

View file

@ -103,8 +103,6 @@ sayHiDeferred("John"); // Hello, John after 2 seconds
The same without an arrow function would look like:
Аналогичная реализация без функции-стрелки выглядела бы так:
```js
function defer(f, ms) {
return function(...args) {

Binary file not shown.