diff --git a/1-js/2-first-steps/21-object-tostring-valueof/article.md b/1-js/2-first-steps/21-object-tostring-valueof/article.md index 3579de6f..9d6f7029 100644 --- a/1-js/2-first-steps/21-object-tostring-valueof/article.md +++ b/1-js/2-first-steps/21-object-tostring-valueof/article.md @@ -40,7 +40,7 @@ The `ToPrimitive(obj, "number")` is the same, but `valueOf()` and `toString()` a 4. If the result is a primitive, return it. 5. Otherwise `TypeError` (conversion failed) -```smart header="ToPrimitive returns a primitive" +```smart header="ToPrimitive returns a primitive, but its type is not guaranteed" As we can see, the result of `ToPrimitive` is always a primitive, because even if `toString/valueOf` return a non-primitive value, it is ignored. But it can be any primitive. There's no control whether `toString()` returns exactly a string or, say a boolean. @@ -96,7 +96,7 @@ If only `toString()` is implemented, then both string and numeric conversions us ## Array example -Let's check a few examples to finally get the whole picture. +Let's see few more examples with arrays to get the better picture. ```js run alert( [] + 1 ); // '1' @@ -108,7 +108,7 @@ The array from the left side of `+` is first converted to primitive using `toPri For arrays (and most other built-in objects) only `toString` is implemented, and it returns a list of items. -So we'll have: +So we'll have the following results of conversion: ```js alert( '' + 1 ); // '1' diff --git a/1-js/5-deeper/1-recursion/9-output-single-linked-list/task.md b/1-js/5-deeper/1-recursion/9-output-single-linked-list/task.md index 7b3e51b2..e87d2cfa 100644 --- a/1-js/5-deeper/1-recursion/9-output-single-linked-list/task.md +++ b/1-js/5-deeper/1-recursion/9-output-single-linked-list/task.md @@ -5,6 +5,7 @@ importance: 5 # Вывести односвязный список TODO: определение списка есть в статье, убрать отсюда. +TODO: разбить на две задачи - прямой и обратный вывод. [Односвязный список](http://ru.wikipedia.org/wiki/Связный_список) -- это структура данных, которая состоит из *элементов*, каждый из которых хранит ссылку на следующий. Последний элемент может не иметь ссылки, либо она равна `null`. diff --git a/1-js/5-deeper/2-closure/1-counter-independent/solution.md b/1-js/5-deeper/2-closure/1-counter-independent/solution.md new file mode 100644 index 00000000..cff69a25 --- /dev/null +++ b/1-js/5-deeper/2-closure/1-counter-independent/solution.md @@ -0,0 +1,5 @@ +The answer: **0,1.** + +Functions `counter` and `counter2` are created by different invocations of `makeCounter`. + +So they have independent outer Lexical Environments, each one has it's own `count`. diff --git a/1-js/5-deeper/2-closure/1-counter-independent/task.md b/1-js/5-deeper/2-closure/1-counter-independent/task.md new file mode 100644 index 00000000..969cc463 --- /dev/null +++ b/1-js/5-deeper/2-closure/1-counter-independent/task.md @@ -0,0 +1,29 @@ +importance: 5 + +--- + +# Are counters independent? + +What is the second counter going to show? `0,1` or `2,3` or something else? + +```js +function makeCounter() { + let count = 0; + + return function() { + return count++; + }; +} + +var counter = makeCounter(); +var counter2 = makeCounter(); + +alert( counter() ); // 0 +alert( counter() ); // 1 + +*!* +alert( counter2() ); // ? +alert( counter2() ); // ? +*/!* +``` + diff --git a/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/solution.js b/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/solution.js new file mode 100644 index 00000000..ce894698 --- /dev/null +++ b/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/solution.js @@ -0,0 +1,13 @@ +function makeCounter() { + let count = 0; + + function counter() { + return count++; + } + + counter.set = value => count = value; + + counter.decrease = () => count--; + + return counter; +} diff --git a/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/source.js b/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/source.js new file mode 100644 index 00000000..5bf29aa2 --- /dev/null +++ b/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/source.js @@ -0,0 +1,18 @@ +function makeCounter() { + let count = 0; + + // ... your code ... +} + +let counter = makeCounter(); + +alert( counter() ); // 0 +alert( counter() ); // 1 + +counter.set(10); // set the new count + +alert( counter() ); // 10 + +counter.decrease(); // decrease the count by 1 + +alert( counter() ); // 10 (instead of 11) diff --git a/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/test.js b/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/test.js new file mode 100644 index 00000000..0e613aba --- /dev/null +++ b/1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/test.js @@ -0,0 +1,41 @@ +describe("counter", function() { + + it("increases from call to call", function() { + + let counter = makeCounter(); + + assert.equal( counter(), 0 ); + assert.equal( counter(), 1 ); + assert.equal( counter(), 2 ); + }); + + + describe("counter.set", function() { + it("sets the count", function() { + + let counter = makeCounter(); + + counter.set(10); + + assert.equal( counter(), 10 ); + assert.equal( counter(), 11 ); + }); + }); + + describe("counter.decrease", function() { + it("decreases the count", function() { + + let counter = makeCounter(); + + counter.set(10); + + assert.equal( counter(), 10 ); + + counter.decrease(); + + assert.equal( counter(), 10 ); + + }); + }); + +}); \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/2-counter-inc-dec/solution.md b/1-js/5-deeper/2-closure/2-counter-inc-dec/solution.md new file mode 100644 index 00000000..038ad899 --- /dev/null +++ b/1-js/5-deeper/2-closure/2-counter-inc-dec/solution.md @@ -0,0 +1,6 @@ + +The solution is to write addition methods right into the `counter`. They share the same outer lexical environment and also can access the current `count`. + +This trick is often used for Javascript libraries like lodash, jQuery and others. They provide a function that has other functions as properties. + +Actually, they do it to less pollute the global space, so that a single library gives only one global variable. That lowers the chance of possible naming conflicts. \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/2-counter-inc-dec/task.md b/1-js/5-deeper/2-closure/2-counter-inc-dec/task.md new file mode 100644 index 00000000..1e67fcc8 --- /dev/null +++ b/1-js/5-deeper/2-closure/2-counter-inc-dec/task.md @@ -0,0 +1,13 @@ +importance: 5 + +--- + +# Set and decrease for counter + +Modify the code of `makeCounter()` so that the counter can also decrease and set the number: + +- `counter()` should return the next number (as before). +- `counter.set(value)` should set the `count` to `value`. +- `counter.decrease(value)` should decrease the `count` by 1. + +See the sandbox code for the complete usage example. \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/3-function-in-if/solution.md b/1-js/5-deeper/2-closure/3-function-in-if/solution.md new file mode 100644 index 00000000..e2e7a91b --- /dev/null +++ b/1-js/5-deeper/2-closure/3-function-in-if/solution.md @@ -0,0 +1,3 @@ +The result is **an error**. + +The function `sayHi` is declared inside the `if`, so it only lives inside it. There is no `sayHi` outside. \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/3-function-in-if/task.md b/1-js/5-deeper/2-closure/3-function-in-if/task.md new file mode 100644 index 00000000..14c2fe21 --- /dev/null +++ b/1-js/5-deeper/2-closure/3-function-in-if/task.md @@ -0,0 +1,18 @@ + +Look at the code. What will be result of the call at the last line? + +```js run +let phrase = "Hello"; + +if (true) { + let user = "John"; + + function sayHi() { + alert(`${phrase}, ${user}`); + } +} + +*!* +sayHi(); +*/!* +``` diff --git a/1-js/5-deeper/2-closure/4-closure-sum/solution.md b/1-js/5-deeper/2-closure/4-closure-sum/solution.md new file mode 100644 index 00000000..e8c8c465 --- /dev/null +++ b/1-js/5-deeper/2-closure/4-closure-sum/solution.md @@ -0,0 +1,17 @@ +For the second brackets to work, the first ones must return a function. + +Like this: + +```js run +function sum(a) { + + return function(b) { + return a + b; // takes "a" from the outer lexical environment + }; + +} + +alert( sum(1)(2) ); // 3 +alert( sum(5)(-1) ); // 4 +``` + diff --git a/1-js/5-deeper/2-closure/4-closure-sum/task.md b/1-js/5-deeper/2-closure/4-closure-sum/task.md new file mode 100644 index 00000000..c2f3eabe --- /dev/null +++ b/1-js/5-deeper/2-closure/4-closure-sum/task.md @@ -0,0 +1,17 @@ +importance: 4 + +--- + +# Sum with closures + +Write function `sum` that works like this: `sum(a)(b) = a+b`. + +Yes, exactly this way, via double brackets (not a mistype). + +For instance: + +```js +sum(1)(2) = 3 +sum(5)(-1) = 4 +``` + diff --git a/1-js/5-deeper/2-closure/5-sum-many-brackets/solution.md b/1-js/5-deeper/2-closure/5-sum-many-brackets/solution.md new file mode 100644 index 00000000..567067b9 --- /dev/null +++ b/1-js/5-deeper/2-closure/5-sum-many-brackets/solution.md @@ -0,0 +1,56 @@ + +1. For the whole thing to work *anyhow*, the result of `sum` must be function. +2. That function must keep in memory the current value between calls. +3. According to the task, the function must become the number when used in `==`. Functions are objects, so as described in the chapter , they use `valueOf` for such conversion. So we should give it the `valueOf` that returns the right number. Or if we want it to behave the same in a string context too, then `toString`. + +Now the code: + +```js run +function sum(a) { + + var currentSum = a; + + function f(b) { + currentSum += b; + return f; + } + + f.toString = function() { + return currentSum; + }; + + return f; +} + +alert( sum(1)(2) ); // 3 +alert( sum(5)(-1)(2) ); // 6 +alert( sum(6)(-1)(-2)(-3) ); // 0 +alert( sum(0)(1)(2)(3)(4)(5) ); // 15 +``` + +Please note that the `sum` function actually works only once. It returns function `f`. + +Then, on each subsequent call, `f` adds its parameter to the sum `currentSum`, and returns itself. + +**There is no recursion in the last line of `f`.** + +Here is what recursion looks like: + +```js +function f(b) { + currentSum += b; + return f(); // <-- recursive call +} +``` + +And in our case, we just return the function, without calling it: + +```js +function f(b) { + currentSum += b; + return f; // <-- does not call itself, returns itself +} +``` + +This `f` will be used in the next call, again return itself, so many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. + diff --git a/1-js/5-deeper/2-closure/5-sum-many-brackets/task.md b/1-js/5-deeper/2-closure/5-sum-many-brackets/task.md new file mode 100644 index 00000000..2d0892e7 --- /dev/null +++ b/1-js/5-deeper/2-closure/5-sum-many-brackets/task.md @@ -0,0 +1,16 @@ +importance: 2 + +--- + +# Sum with an arbitrary amount of brackets + +Write function `sum` that would work like this: + +```js +sum(1)(2) == 3; // 1 + 2 +sum(1)(2)(3) == 6; // 1 + 2 + 3 +sum(5)(-1)(2) == 6 +sum(6)(-1)(-2)(-3) == 0 +sum(0)(1)(2)(3)(4)(5) == 15 +``` + diff --git a/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/solution.js b/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/solution.js new file mode 100644 index 00000000..66a149d9 --- /dev/null +++ b/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/solution.js @@ -0,0 +1,8 @@ + +function inArray(arr) { + return x => arr.includes(x); +} + +function inBetween(a, b) { + return x => (x >= a && x <= b); +} \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/source.js b/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/source.js new file mode 100644 index 00000000..26e19c75 --- /dev/null +++ b/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/source.js @@ -0,0 +1,8 @@ + +function inBetween(a, b) { + // ...your code... +} + +function inArray(arr) { + // ...your code... +} diff --git a/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/test.js b/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/test.js new file mode 100644 index 00000000..5dd5d027 --- /dev/null +++ b/1-js/5-deeper/2-closure/6-filter-through-function/_js.view/test.js @@ -0,0 +1,22 @@ + +describe("inArray", function() { + let arr = [1, 2, 3, 4, 5, 6, 7]; + + it("returns the filter for values in array", function() { + + let filter = inArray(arr); + assert.isTrue(filter(5)); + assert.isFalse(filter(0)); + }); +}); + + +describe("inBetween", function() { + + it("returns the filter for values between", function() { + let filter = inBetween(3, 6); + assert.isTrue(filter(5)); + assert.isFalse(filter(0)); + }); +}); + diff --git a/1-js/5-deeper/2-closure/6-filter-through-function/solution.md b/1-js/5-deeper/2-closure/6-filter-through-function/solution.md new file mode 100644 index 00000000..b9657971 --- /dev/null +++ b/1-js/5-deeper/2-closure/6-filter-through-function/solution.md @@ -0,0 +1,79 @@ +# Функция фильтрации + +```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 +``` + +# Фильтр 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; + }; + } +*/!* + +var arr = [1, 2, 3, 4, 5, 6, 7]; +alert( filter(arr, inArray([1, 2, 10])) ); // 1,2 +``` + diff --git a/1-js/5-deeper/2-closure/6-filter-through-function/task.md b/1-js/5-deeper/2-closure/6-filter-through-function/task.md new file mode 100644 index 00000000..8d78bf96 --- /dev/null +++ b/1-js/5-deeper/2-closure/6-filter-through-function/task.md @@ -0,0 +1,29 @@ +importance: 5 + +--- + +# Filter through function + +We have a built-in method `arr.filter(f)` for arrays. It filters all elements through the function `f`. If it returns `true`, then such an element is returned in the resulting array. + +Make a set of "ready to use" filters: + +- `inBetween(a, b)` -- between `a` and `b` or equal to them (inclusively). +- `inArray([...])` -- in the given array. + +The usage must be like this: + +- `arr.filter(inBetween(3,6))` -- selects only values between 3 and 6. +- `arr.filter(inArray([1,2,3]))` -- selects only elements matching with one of the members of `[1,2,3]`. + +For instance: + +```js +/* .. your code for inBetween and inArray */ +let arr = [1, 2, 3, 4, 5, 6, 7]; + +alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 + +alert( arr.filter(inArray([1, 2, 10])) ); // 1,2 +``` + diff --git a/1-js/5-deeper/2-closure/7-sort-by-field/solution.md b/1-js/5-deeper/2-closure/7-sort-by-field/solution.md new file mode 100644 index 00000000..bd57085e --- /dev/null +++ b/1-js/5-deeper/2-closure/7-sort-by-field/solution.md @@ -0,0 +1,22 @@ + + +```js run +let users = [ + { name: "John", age: 20, surname: "Johnson" }, + { name: "Pete", age: 18, surname: "Peterson" }, + { name: "Ann", age: 19, surname: "Hathaway" } +]; + +*!* +function byField(field) { + return (a, b) => a[field] > b[field] ? 1 : -1; +} +*/!* + +users.sort(byField('name')); +users.forEach(user => alert(user.name)); // Ann, John, Pete + +users.sort(byField('age')); +users.forEach(user => alert(user.name)); // Pete, Ann, John +``` + diff --git a/1-js/5-deeper/2-closure/7-sort-by-field/task.md b/1-js/5-deeper/2-closure/7-sort-by-field/task.md new file mode 100644 index 00000000..181fa6ff --- /dev/null +++ b/1-js/5-deeper/2-closure/7-sort-by-field/task.md @@ -0,0 +1,36 @@ +importance: 5 + +--- + +# Sort by field + +We've got an array of objects to sort: + +```js +let users = [ + { name: "John", age: 20, surname: "Johnson" }, + { name: "Pete", age: 18, surname: "Peterson" }, + { name: "Ann", age: 19, surname: "Hathaway" } +]; +``` + +The usual way to do that would be: + +```js +// by name (Ann, John, Pete) +users.sort((a, b) => a.name > b.name ? 1 : -1); + +// по age (Pete, Ann, John) +users.sort((a, b) => a.age > b.age ? 1 : -1); +``` + +Can we make it even more verbose, like this? + +```js +users.sort(byField('name')); +users.sort(byField('age')); +``` + +So, instead of writing a function, just put `byField(fieldName)`. + +Write the function `byField` that can be used for that. \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/8-make-army/_js.view/solution.js b/1-js/5-deeper/2-closure/8-make-army/_js.view/solution.js new file mode 100644 index 00000000..2455c301 --- /dev/null +++ b/1-js/5-deeper/2-closure/8-make-army/_js.view/solution.js @@ -0,0 +1,19 @@ +function makeArmy() { + + var shooters = []; + + for (var i = 0; i < 10; i++) { + + var shooter = (function(x) { + + return function() { + alert(x); + }; + + })(i); + + shooters.push(shooter); + } + + return shooters; +} \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/8-make-army/_js.view/source.js b/1-js/5-deeper/2-closure/8-make-army/_js.view/source.js new file mode 100644 index 00000000..f9058674 --- /dev/null +++ b/1-js/5-deeper/2-closure/8-make-army/_js.view/source.js @@ -0,0 +1,13 @@ +function makeArmy() { + + var shooters = []; + + for (var i = 0; i < 10; i++) { + var shooter = function() { // функция-стрелок + alert(i); // выводит свой номер + }; + shooters.push(shooter); + } + + return shooters; +} \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/8-make-army/_js.view/test.js b/1-js/5-deeper/2-closure/8-make-army/_js.view/test.js new file mode 100644 index 00000000..fdfabf66 --- /dev/null +++ b/1-js/5-deeper/2-closure/8-make-army/_js.view/test.js @@ -0,0 +1,20 @@ +var army; +before(function() { + army = makeArmy(); + window.alert = sinon.stub(window, "alert"); +}); + +it("army[0] выводит 0", function() { + army[0](); + assert(alert.calledWith(0)); +}); + + +it("army[5] функция выводит 5", function() { + army[5](); + assert(alert.calledWith(5)); +}); + +after(function() { + window.alert.restore(); +}); \ No newline at end of file diff --git a/1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy.png b/1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy.png new file mode 100644 index 00000000..72266fca Binary files /dev/null and b/1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy.png differ diff --git a/1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy@2x.png b/1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy@2x.png new file mode 100644 index 00000000..c9dd8817 Binary files /dev/null and b/1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy@2x.png differ diff --git a/1-js/5-deeper/2-closure/8-make-army/solution.md b/1-js/5-deeper/2-closure/8-make-army/solution.md new file mode 100644 index 00000000..94e327b5 --- /dev/null +++ b/1-js/5-deeper/2-closure/8-make-army/solution.md @@ -0,0 +1,104 @@ + +The function `makeArmy` does the following: + +1. Creates an empty array `shooters`: + + ```js + let shooters = []; + ``` +2. Fills it in the loop via `shooters.push`. + + Every element is a function, so the resulting array looks like this: + + ```js no-beautify + shooters = [ + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); } + ]; + ``` + +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. + +Now why all shooters 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. + +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`. + +As a result, all `shooter` functions get from the outer lexical envrironment the same, last value `i=10`. + +The fix can be very simple: + +```js run +function makeArmy() { + + let shooters = []; + +*!* + for(let i = 0; i < 10; i++) { +*/!* + let shooter = function() { // shooter function + alert( i ); // should show its number + }; + shooters.push(shooter); + } + + return shooters; +} + +let army = makeArmy(); + +army[0](); // 0 +army[5](); // 5 +``` + +Now it works correctly, because every time the code block in `for (..) {...}` is executed, a new Lexical Environment is created for it, with the corresponding value of `i`. + +So, the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration. A `shooter` gets the value exactly from the one where it was created. + +![](lexenv-makearmy.png) + +Here we rewrote `while` into `for`. But it is also possible to keep the existing `while` structure: + + +```js run +function makeArmy() { + let shooters = []; + + let i = 0; + while (i < 10) { +*!* + let j = i; +*/!* + let shooter = function() { // shooter function + alert( *!*j*/!* ); // should show its number + }; + shooters.push(shooter); + i++; + } + + return shooters; +} + +let army = makeArmy(); + +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. + +We copy `let j = i`. This makes a loop-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. + diff --git a/1-js/5-deeper/2-closure/8-make-army/task.md b/1-js/5-deeper/2-closure/8-make-army/task.md new file mode 100644 index 00000000..6495984e --- /dev/null +++ b/1-js/5-deeper/2-closure/8-make-army/task.md @@ -0,0 +1,36 @@ +importance: 5 + +--- + +# Army of functions + +The following code creates an array of `shooters`. + +Every function is meant to output its number. But something is wrong... + +```js run +function makeArmy() { + let shooters = []; + + let i = 0; + while (i < 10) { + let j = i; + let shooter = function() { // shooter function + alert( j ); // 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... +``` + +Why all shooters show the same? Fix the code so that they work as intended. + diff --git a/1-js/5-deeper/2-closure/article.md b/1-js/5-deeper/2-closure/article.md index 1d8540c6..b42c503d 100644 --- a/1-js/5-deeper/2-closure/article.md +++ b/1-js/5-deeper/2-closure/article.md @@ -90,13 +90,13 @@ Now let's add a function: ![lexical environment](lexical-environment-global-3.png) -Here we see an important moment related to Function Declarations. They are processed when a Lexical Environment is created. For the global Lexical Environment, it means the moment when the script is started. So if we wanted to call `say()` before the declaration, it would work. +Here we see an important moment related to Function Declarations. They are processed when a Lexical Environment is created. For the global Lexical Environment, it means the moment when the script is started. So `say` exists from the very beginning (and we can call it prior to declaration). The `let` definition is processed normally, so it is added later. -Now let's run the function. +Now let's run the function `say()`. -When it runs, a function Lexical Environment is created automatically, for variables and parameters of the current function call: +When it runs, the new function Lexical Environment is created automatically, for variables and parameters of the current function call: -```js + +```js untrusted run no-strict refresh var phrase = "Hello"; -let user = "John"; function sayHi() { - alert(phrase + ', ' + user); + alert(phrase); } -alert( window.phrase ); // Hello -alert( window.sayHi ); // function +// can read from window +alert( window.phrase ); // Hello (global var) +alert( window.sayHi ); // function (global function declaration) -*!* -alert( window.user ); // undefined -*/!* +// can write to window (creates a new sglobal variable) +window.test = 5; + +alert(test); // 5 ``` -In the example above you can clearly see that `let user` is not in `window`. +...But the global object does not have variables declared with `let/const`: -That's because the idea of a global object as a way to access "all global things" comes from ancient times. In modern scripts its use is not recommended, and modern language features like `let/const` do not make friends with it. +```js untrusted run no-strict refresh +*!*let*/!* user = "John"; +alert(user); // John + +alert(window.user); // undefined, don't have let +alert("user" in window); // false +``` + +Here you can clearly see that `let user` is not in `window`. + +That's because the idea of a global object as a way to access "all global things" comes from ancient times. Nowadays is not considered to be a good thing. Modern language features like `let/const` do not make friends with it, but old ones try to be compatible. + +### Uses of "window" + +In server-side environments like Node.JS, the `global` object is used exceptionally rarely. Probably it would be fair to say "never". + +In-browser `window` is sometimes used for following purposes: + +1. To access exactly the global variable if the function has the local one with the same name. + + ```js untrusted run no-strict refresh + var user = "Global"; + + function sayHi() { + var user = "Local"; + + *!* + alert(window.user); // Global + */!* + } + + sayHi(); + ``` + + Such use is typically a workaround. Would be better to name variables in a way that does require to write it this way. And note the `var user`. The trick doesn't work with `let` variables. + +2. To check if a certain global variable or a builtin exists. + + For instance, we want to check whether a global function `XMLHttpRequest` exists. + + We can't write `if (XMLHttpRequest)`, because if there's no such global, that's an access to undefined variable, an error. + + But we can get it via `window.XMLHttpRequest`: + + ```js run + if (window.XMLHttpRequest) { + alert('XMLHttpRequest exists!') + } + ``` + + If there is no such global function then `window.XMLHttpRequest` is just an access to unexisting object property. That's `undefined`, no error, so it works. + + We can also write the test without `window`: + + ```js + if (typeof XMLHttpRequest == 'function') { + /* is there a function XMLHttpRequest? */ + /* this will also use a local XMLHttpRequest if exists */ + } + ``` -TODO +3. The rare, special in-browser usage is to take the variable from the right window. -As said ed in the [specification](https://tc39.github.io/ecma262/#sec-lexical-environments), the global object provides access to *some* global variables. + A browser may open multiple windows and tabs. A window may also embed another one in ` + + + ``` + +````smart header="Window and \"this\"" +As we know, usually `this` is used inside an object method to access the object. + +But outside of that, sometimes `this` equals `window`: + +- The value of the global `this` is `window`: + + ```js run + // outside of functions + alert( this === window ); // true + ``` + +- When a function with `this` is called in not-strict mode: + ```js run no-strict + // not in strict mode + function f() { + alert(this); // [object Window] + } + + f(); // called without an object + ``` + That's for compatibility. With `use strict` in the last example `this` would be `undefined`. + +```` -The key word here is "some". In practice: - -- `Array`, `Object`, `alert`, `prompt` and other built built-in functions and variables and Function Decla -- let not in -- var is in -- FDs are in - - - - - - -## old - - -- Technically, when a function is created, it gets a special hidden property named `[[Environment]]` that keeps a reference to the current Lexical Environment. -- When the function is called, a new Lexical Environment is created - - -For `sayHi` to access `name`, it has to go to the global Lexical Environment. - - - -variable is a may be visible in the : - -- A whole script (global variables) - -A whole script or a function or a code block in Javascript may - -A function can access outer variables. - - -- A variable can be defined with `var`, `let` or `const` -- -that a function can access variables outside of it. - diff --git a/1-js/5-deeper/3-new-function/article.md b/1-js/5-deeper/3-new-function/article.md new file mode 100644 index 00000000..0f45e4ff --- /dev/null +++ b/1-js/5-deeper/3-new-function/article.md @@ -0,0 +1,94 @@ + +# The exclusion: new Function + +There is one exclusion from the behavior of nested function. + +When a function is created using `new Function`, its `[[Environment]]` references not the current Lexical Environment, got the global one. + +## Example + +The `new Function` syntax is used rarely so let's remember it: + +```js run +let sum = new Function('arg1, arg2', 'return arg1 + arg2'); + +alert( sum(1, 2) ); // 3 +``` + +It creates the function dynamically from the string. The first argument is a comma-separated list of arguments, the second one is the code. + +Here's the example that shows how a function created with `new Function` ignores the outer variable `a`: + + +```js run untrusted refresh +function getFunc() { + let a = 1; + +*!* + let func = new Function('', 'alert(a)'); +*/!* + + return func; +} + +getFunc()(); // error: a is not defined +// it would show the global "a" if we had any. +``` + + +Compare it with the regular behavior: + +```js run untrusted refresh +function getFunc() { + let a = 2; + +*!* + let func = function() { alert(a); }; +*/!* + + return func; +} + +getFunc()(); // *!*1*/!*, from the Lexical Environment of getFunc +``` + +## Where is it useful? + +```warn header="Advanced knowledge" +The subsection describes advanced practical use cases. To know them is not required to continue studying Javascript. +``` + +This "speciality" of `new Function` looks strange, but appears very useful in practice. + +Imagine that we really have to create a function from the string. The code of that function is not known at the time of writing the script (that's why we don't use regular functions), but will be known in the future. We can receive it from the server or from another source. + +That new function needs to interact with the main script. + +But the problem is that before Javascript is published to production, it's compressed using a *minifier* -- a special program that shrinks code by removing extra comments, spaces and -- what's important, renames local variables into shorter ones. + +So, if a function has `let userName`, then a minifier replaces it `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, not just find-and-replace the text. + +...So if `new Function` could access outer variables, then it would be unable to find `userName`. + +**Even if we could access outer lexical environment in `new Function`, we would have problems with minifiers.** + +The "special feature" of `new Function` saves us from mistakes. + +If we need to pass something to a functionc created by `new Function`, we should pass them explicitly as arguments, for instance: + +```js run untrusted refresh no-beautify +*!* +let sum = new Function('a, b', ' return a + b; '); +*/!* + +let a = 1, b = 2; + +*!* +alert( sum(a, b) ); // 3 +*/!* +``` + +## Summary + +- Functions created with `new Function`, have `[[Environment]]` referencing the global Lexical Environment, not the outer one. +- Hence, they can not use outer variables. But that's actually good, because it saves us from errors. Explicit parameters passing is a much better thing architecturally and has no problems with minifiers. \ No newline at end of file diff --git a/1-js/plan2.txt b/1-js/plan2.txt new file mode 100644 index 00000000..30304ce5 --- /dev/null +++ b/1-js/plan2.txt @@ -0,0 +1,89 @@ +getting-started + introduction + editor + devtools +first-steps + hello-world (intro, scripts blocks rendering) + external-script (TODO: move defer/async out to browser part) + structure + use strict + variables (const, name things right) + types (objects, arrays create/access props, typeof) + type-conversions (conversions string/number/boolean, objects in general words, no toString/valueOf) + operators + comparison (note about toprimitive) + destructuring (Q: move from here? need in objects) + uibasic + logical-ops + while-for (labels, for..in, for..of) + switch + function-basics (decl, shadowing, naming) + function-parameters (default params, rest, destructuring, spread, TODO, Q: move from here?) + function-create-advanced (function is object, FE as a method, user with methods, arrow basics NO THIS, new function) + object-methods (this, method syntax, call/apply) + object-tostring-valueof (also @@toStringTag) + primitives-methods (on-the-fly objects) + javascript-specials (TODO, remove it? migrate all function* to separate chapter?) +code-quality + debugging-chrome (TODO) + coding-style (TODO) + write-unmaintainable-code (TODO) + test-driven-development (TODO) + polyfills (TODO) +data-structures + number (rounding, precision, isFinite, isNaN, parse*, Math.*) + string (quotes, search, substring, tagged template notice) + object (loops++, copying, shorthands) + - other information would be too much + array (TODO: where is iterable?) + array-methods (TODO: tasks) + - would be too much + date (TODO: tasks) + map-set-weakmap-weakset + +------- + + <<< json? +<<< descriptors (TODO: LATER, need JSON to output, better after inheritance to explain getOwnProps) +<<< getter setter + + +recursion ( + running execution context = where + lexical environment = envrec + outer + context stack + pow task + traverse list task + task: traverse list back +) +closures + LE outer + returning a function + -not function props + new Function? + counter object? + +bind, currying +decorators +constructors +classes +instanceof +IIFE ? + +<<< memory management? (AFTER CLOSURES! Сюрприз нельзя получить!!!) + + +после 4-object сделать + descriptors + + + +more-features + try..catch + setTimeout + JSON + + +====== +A global environment is a Lexical Environment which does not have an outer environment. The global environment's outer environment reference is null. A global environment's EnvironmentRecord may be prepopulated with identifier bindings and includes an associated global object whose properties provide some of the global environment's identifier bindings. (window has SOME global env bindings!!!) +===== +class A extends Object != class A diff --git a/figures.sketch b/figures.sketch index 32f8a593..cea3b4b2 100644 Binary files a/figures.sketch and b/figures.sketch differ