up
This commit is contained in:
parent
e002be4356
commit
28d3b85a1a
33 changed files with 1069 additions and 95 deletions
|
@ -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'
|
||||
|
|
|
@ -5,6 +5,7 @@ importance: 5
|
|||
# Вывести односвязный список
|
||||
|
||||
TODO: определение списка есть в статье, убрать отсюда.
|
||||
TODO: разбить на две задачи - прямой и обратный вывод.
|
||||
|
||||
[Односвязный список](http://ru.wikipedia.org/wiki/Связный_список) -- это структура данных, которая состоит из *элементов*, каждый из которых хранит ссылку на следующий. Последний элемент может не иметь ссылки, либо она равна `null`.
|
||||
|
||||
|
|
|
@ -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`.
|
29
1-js/5-deeper/2-closure/1-counter-independent/task.md
Normal file
29
1-js/5-deeper/2-closure/1-counter-independent/task.md
Normal file
|
@ -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() ); // ?
|
||||
*/!*
|
||||
```
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
function makeCounter() {
|
||||
let count = 0;
|
||||
|
||||
function counter() {
|
||||
return count++;
|
||||
}
|
||||
|
||||
counter.set = value => count = value;
|
||||
|
||||
counter.decrease = () => count--;
|
||||
|
||||
return counter;
|
||||
}
|
18
1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/source.js
Normal file
18
1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/source.js
Normal file
|
@ -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)
|
41
1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/test.js
Normal file
41
1-js/5-deeper/2-closure/2-counter-inc-dec/_js.view/test.js
Normal file
|
@ -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 );
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
6
1-js/5-deeper/2-closure/2-counter-inc-dec/solution.md
Normal file
6
1-js/5-deeper/2-closure/2-counter-inc-dec/solution.md
Normal file
|
@ -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.
|
13
1-js/5-deeper/2-closure/2-counter-inc-dec/task.md
Normal file
13
1-js/5-deeper/2-closure/2-counter-inc-dec/task.md
Normal file
|
@ -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.
|
3
1-js/5-deeper/2-closure/3-function-in-if/solution.md
Normal file
3
1-js/5-deeper/2-closure/3-function-in-if/solution.md
Normal file
|
@ -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.
|
18
1-js/5-deeper/2-closure/3-function-in-if/task.md
Normal file
18
1-js/5-deeper/2-closure/3-function-in-if/task.md
Normal file
|
@ -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();
|
||||
*/!*
|
||||
```
|
17
1-js/5-deeper/2-closure/4-closure-sum/solution.md
Normal file
17
1-js/5-deeper/2-closure/4-closure-sum/solution.md
Normal file
|
@ -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
|
||||
```
|
||||
|
17
1-js/5-deeper/2-closure/4-closure-sum/task.md
Normal file
17
1-js/5-deeper/2-closure/4-closure-sum/task.md
Normal file
|
@ -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
|
||||
```
|
||||
|
56
1-js/5-deeper/2-closure/5-sum-many-brackets/solution.md
Normal file
56
1-js/5-deeper/2-closure/5-sum-many-brackets/solution.md
Normal file
|
@ -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 <info:object-tostring-valueof>, 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`.
|
||||
|
16
1-js/5-deeper/2-closure/5-sum-many-brackets/task.md
Normal file
16
1-js/5-deeper/2-closure/5-sum-many-brackets/task.md
Normal file
|
@ -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
|
||||
```
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
function inArray(arr) {
|
||||
return x => arr.includes(x);
|
||||
}
|
||||
|
||||
function inBetween(a, b) {
|
||||
return x => (x >= a && x <= b);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
function inBetween(a, b) {
|
||||
// ...your code...
|
||||
}
|
||||
|
||||
function inArray(arr) {
|
||||
// ...your code...
|
||||
}
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
```
|
||||
|
29
1-js/5-deeper/2-closure/6-filter-through-function/task.md
Normal file
29
1-js/5-deeper/2-closure/6-filter-through-function/task.md
Normal file
|
@ -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
|
||||
```
|
||||
|
22
1-js/5-deeper/2-closure/7-sort-by-field/solution.md
Normal file
22
1-js/5-deeper/2-closure/7-sort-by-field/solution.md
Normal file
|
@ -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
|
||||
```
|
||||
|
36
1-js/5-deeper/2-closure/7-sort-by-field/task.md
Normal file
36
1-js/5-deeper/2-closure/7-sort-by-field/task.md
Normal file
|
@ -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.
|
19
1-js/5-deeper/2-closure/8-make-army/_js.view/solution.js
Normal file
19
1-js/5-deeper/2-closure/8-make-army/_js.view/solution.js
Normal file
|
@ -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;
|
||||
}
|
13
1-js/5-deeper/2-closure/8-make-army/_js.view/source.js
Normal file
13
1-js/5-deeper/2-closure/8-make-army/_js.view/source.js
Normal file
|
@ -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;
|
||||
}
|
20
1-js/5-deeper/2-closure/8-make-army/_js.view/test.js
Normal file
20
1-js/5-deeper/2-closure/8-make-army/_js.view/test.js
Normal file
|
@ -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();
|
||||
});
|
BIN
1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy.png
Normal file
BIN
1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy@2x.png
Normal file
BIN
1-js/5-deeper/2-closure/8-make-army/lexenv-makearmy@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
104
1-js/5-deeper/2-closure/8-make-army/solution.md
Normal file
104
1-js/5-deeper/2-closure/8-make-army/solution.md
Normal file
|
@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
36
1-js/5-deeper/2-closure/8-make-army/task.md
Normal file
36
1-js/5-deeper/2-closure/8-make-army/task.md
Normal file
|
@ -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.
|
||||
|
|
@ -90,13 +90,13 @@ Now let's add a function:
|
|||
|
||||

|
||||
|
||||
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
|
||||
|
@ -121,7 +121,7 @@ We now have two Lexical Environments: the inner (for the function call) and the
|
|||
As we'll see further, functions can be more nested, so the chain of outer references can be longer.
|
||||
|
||||
|
||||
Please note that the Lexical Environment for function `say` only exists while the function is executing! There are no local variables or something until the function is actually called. And if the function is called multiple times, then each invocation has it's own Lexical Environment, with local variables and parameters for the current run.
|
||||
Please note that the Lexical Environment for function `say` is only created when the function starts executing! And if the function is called multiple times, then each invocation has it's own Lexical Environment, with local variables and parameters for the current run.
|
||||
|
||||
**When a code wants to access a variable -- it is first searched in the current Lexical Environment, then in the outer one, and further until the end of the chain.**
|
||||
|
||||
|
@ -219,7 +219,7 @@ The questions may arise:
|
|||
|
||||
1. How it works?
|
||||
2. What happens if there is a global variable named `count`? Can it confuse the `counter`?
|
||||
3. What if we call `makeCounter` multiple times? Are the resulting `counter` functions independant or they share the same count?
|
||||
3. What if we call `makeCounter` multiple times? Are the resulting `counter` functions independent or they share the same count?
|
||||
|
||||
We'll answer the 1st question and the other ones will also become obvious.
|
||||
|
||||
|
@ -237,6 +237,8 @@ No matter where the function is called, the rule is the same.
|
|||
|
||||
**If a variable is modified, it is modified on the place where it is found, so future accesses will get the updated variant.**
|
||||
|
||||
So `count++` finds the outer variable and increases it "at place" every time, thus returning the next value every time.
|
||||
|
||||
The rule is good for eyes and usually enough, but in more complex situations, the more solid understanding of internals may be needed. So here you go.
|
||||
|
||||
## Environments in detail
|
||||
|
@ -298,7 +300,7 @@ The `work()` function in the code below uses the `name` from the place of its or
|
|||
|
||||

|
||||
|
||||
...But if there were no `name` in `makeWorker()`, then the search would go outside and take the global variable.
|
||||
...But if there were no `name` in `makeWorker()`, then the search would go outside and take the global variable as we can see from the chain above.
|
||||
|
||||
The same for `counter()` calls. The closest outer variable is always used.
|
||||
|
||||
|
@ -312,14 +314,70 @@ That is: all of them automatically remember where they are created using a hidde
|
|||
When on an interview a frontend developer gets a question about "what's a closure?", the valid answer would be a definition of the closure and an explanation that all functions in Javascript are closures, and maybe few more words about technical details: the `[[Envrironment]]` property and how Lexical Environments work.
|
||||
```
|
||||
|
||||
[todo What happens with many makeCounter - are they independant? task!]
|
||||
### An alternative: function properties
|
||||
|
||||
An alternative approach to the counter could be a function property:
|
||||
|
||||
```js run
|
||||
function makeCounter() {
|
||||
|
||||
function counter() {
|
||||
return counter.count++;
|
||||
};
|
||||
|
||||
counter.count = 0;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
let counter = makeCounter();
|
||||
alert( counter() ); // 0
|
||||
alert( counter() ); // 1
|
||||
```
|
||||
|
||||
Unlike the previous example, the current `count` is now bound to the function directly, not to its outer Lexical Environment.
|
||||
|
||||
|
||||
```smart header="Reminder"
|
||||
As we remember, functions are objects in Javascript. So we can store things is them.
|
||||
|
||||
But properties like `counter.count` have nothing in common with function variables. Variables never use function properties and vise versa. These are just parallel words.
|
||||
```
|
||||
|
||||
Which approach is better?
|
||||
|
||||
The main difference is that if the value of `count` lives in a variable, then an external code is unable to access it. Only the nested function may modify it.
|
||||
|
||||
And if it's bound to function, then such thing is possible:
|
||||
|
||||
|
||||
```js run
|
||||
function makeCounter() {
|
||||
|
||||
function counter() {
|
||||
return counter.count++;
|
||||
};
|
||||
|
||||
counter.count = 0;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
let counter = makeCounter();
|
||||
|
||||
*!*
|
||||
counter.count = 10;
|
||||
alert( counter() ); // 10
|
||||
*/!*
|
||||
```
|
||||
|
||||
Sometimes such possibility can be a plus, but usually we want more control over `count`, and the other way is prefered.
|
||||
|
||||
## Code blocks and loops
|
||||
|
||||
A code block has it's own Lexical Environment and hence local variables.
|
||||
|
||||
In the example below, initially there's only global Lexical Environment. When the execution goes into `if` block, the new Lexical Environment is created for it:
|
||||
In the example below, when the execution goes into `if` block, the new Lexical Environment is created for it:
|
||||
|
||||
<!--
|
||||
```js run
|
||||
|
@ -338,17 +396,20 @@ alert(user); // Error, can't see such variable!
|
|||
|
||||

|
||||
|
||||
The new Lexical Environment gets the enclosing one as the outer reference, so `phrase` can be found.
|
||||
The new Lexical Environment gets the enclosing one as the outer reference, so `phrase` can be found. But all variables and Function Expressions declared inside `if`, will reside in that Lexical Environment.
|
||||
|
||||
After `if` finishes, its Lexical Environment is normally destroyed (unless there's a living nested function). That's why the `alert` below doesn't see the `user`.
|
||||
After `if` finishes, its Lexical Environment is normally destroyed (unless there's a living nested function). That's why the `alert` below won't see the `user`.
|
||||
|
||||
We also can use a "bare" code block to isolate variables.
|
||||
**We also can use a "bare" code block to isolate variables.**
|
||||
|
||||
For instance, in-browser all scripts share the same global area. So if we write `message` in one script, it becomes available to others. If we don't want that, can use a code block:
|
||||
For instance, in-browser all scripts share the same global area. So if we create a global variable in one script, it becomes available to others. That may be a source of conflicts if two scripts use the same variable name and overwrite each other.
|
||||
|
||||
If we don't want that, we can use a code block to isolate the whole script or an area in it:
|
||||
|
||||
```js run
|
||||
{
|
||||
// All variables here are only visible to the local script
|
||||
// do some job with local variables that should not be seen outside
|
||||
|
||||
let message = "Hello";
|
||||
|
||||
alert(message); // Hello
|
||||
|
@ -366,7 +427,7 @@ for(let i = 0; i < 10; i++) {
|
|||
}
|
||||
```
|
||||
|
||||
## The old style: "var"
|
||||
## The old "var"
|
||||
|
||||
In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration:
|
||||
|
||||
|
@ -376,9 +437,7 @@ In the very first chapter about [variables](info:variables), we mentioned three
|
|||
|
||||
Here we only talked about `let`. But `const` behaves totally the same way in terms of Lexical Environments.
|
||||
|
||||
The `var` is a very different beast, coming from old times. It's generally not used in modern scripts, but still lurks in the old ones.
|
||||
|
||||
If you don't plan meeting such scripts you may even skip this subsection and return if/when this beasts bites
|
||||
The `var` is a very different beast, coming from old times. It's generally not used in modern scripts, but still lurks in the old ones. If you don't plan meeting such scripts you may even skip this subsection or postpone until it bites.
|
||||
|
||||
From the first sight, `var` behaves similar to `let`:
|
||||
|
||||
|
@ -396,7 +455,7 @@ alert(phrase); // Error, phrase is not defined
|
|||
|
||||
...But let's list the differences.
|
||||
|
||||
`var` variables only recognize function and global Lexical Environments, they ignore blocks.
|
||||
`var` variables only live in function and global Lexical Environments, they ignore blocks.
|
||||
: For instance:
|
||||
|
||||
```js
|
||||
|
@ -409,7 +468,7 @@ alert(phrase); // Error, phrase is not defined
|
|||
*/!*
|
||||
```
|
||||
|
||||
If we used `let test`, then it wouldn't be visible to `alert`. But `var` variables ignore code blocks, so here we've got a global `test`.
|
||||
If we used `let test` on the 2nd line, then it wouldn't be visible to `alert`. But `var` variables ignore code blocks, so here we've got a global `test`.
|
||||
|
||||
The same thing for loops:
|
||||
|
||||
|
@ -423,10 +482,25 @@ alert(phrase); // Error, phrase is not defined
|
|||
*/!*
|
||||
```
|
||||
|
||||
As we can see, `var` pierces through `if`, `for` or other code blocks. That's because some time ago, in Javascript blocks had no Lexical Environments. And `var` is a reminiscence of that.
|
||||
If a code block in inside a function, then `var` becomes a function-level variable:
|
||||
|
||||
`var` declarations are processed when the function starts (or when the script starts for globals).
|
||||
: Technically, it means that all `var` variables are defined from the beginning of the function.
|
||||
```js
|
||||
function sayHi() {
|
||||
if (true) {
|
||||
var phrase = "Hello";
|
||||
}
|
||||
|
||||
alert(phrase); // works
|
||||
}
|
||||
|
||||
sayHi();
|
||||
alert(phrase); // Error: phrase is not defined
|
||||
```
|
||||
|
||||
As we can see, `var` pierces through `if`, `for` or other code blocks. That's because long time ago in Javascript blocks had no Lexical Environments. And `var` is a reminiscence of that.
|
||||
|
||||
`var` declarations are processed when the function starts (or script starts for globals).
|
||||
: In other words, unlike `let/const` that appear at the moment of their declaration, `var` variables are defined from the beginning of the function.
|
||||
|
||||
So this code:
|
||||
|
||||
|
@ -442,7 +516,7 @@ alert(phrase); // Error, phrase is not defined
|
|||
}
|
||||
```
|
||||
|
||||
...Is technically the same as:
|
||||
...Is technically the same as this:
|
||||
|
||||
```js
|
||||
function sayHi() {
|
||||
|
@ -455,7 +529,7 @@ alert(phrase); // Error, phrase is not defined
|
|||
}
|
||||
```
|
||||
|
||||
...Or even like this (remember, code blocks are ignored):
|
||||
...Or even as this (remember, code blocks are ignored):
|
||||
|
||||
```js
|
||||
function sayHi() {
|
||||
|
@ -473,6 +547,8 @@ alert(phrase); // Error, phrase is not defined
|
|||
|
||||
People also call such behavior "hoisting" (raising), because all `var` are "hoisted" (raised) to the top of the function.
|
||||
|
||||
So in the example above, `if (false)` branch never executes, but that doesn't matter. The `var` inside it is processed in the beginning of the function.
|
||||
|
||||
**The pitfall is that assignments are not hoisted**.
|
||||
|
||||
For instance:
|
||||
|
@ -489,27 +565,30 @@ alert(phrase); // Error, phrase is not defined
|
|||
sayHi();
|
||||
```
|
||||
|
||||
The line `var phrase = "Hello"` has two actions in it: variable declaration and assignment.
|
||||
The line `var phrase = "Hello"` has two actions in it: variable declaration `var` and assignment `=`.
|
||||
|
||||
The declaration is hoisted, but the assignment is not. The `alert` works, because the variable is defined from the start of the function. But its value is assigned below, so it shows `undefined`.
|
||||
|
||||
The code is essentially the same as:
|
||||
The declaration is hoisted, but the assignment is not. So the code works essentially as this:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
*!*
|
||||
var phrase;
|
||||
var phrase; // variable is declared from the top...
|
||||
*/!*
|
||||
|
||||
alert(phrase); // undefined
|
||||
|
||||
phrase = "Hello";
|
||||
*!*
|
||||
phrase = "Hello"; // ...but assigned when the execution reaches this line.
|
||||
*/!*
|
||||
}
|
||||
|
||||
sayHi();
|
||||
```
|
||||
|
||||
The features described above make using `var` inconvenient most of time. Because we can't create block-local variables. And hoisting just creates more space for errors. So, once again, nowadays, `vars` are used exceptionally rarely. But they exist in old scripts.
|
||||
The `alert` works, because the variable is defined from the start of the function. But its value is assigned below, so it shows `undefined`.
|
||||
|
||||
|
||||
The features described above make using `var` inconvenient most of time. First, we can't create block-local variables. And hoisting just creates more space for errors. So, once again, for new scripts `var` is used exceptionally rarely.
|
||||
|
||||
|
||||
|
||||
|
@ -520,7 +599,7 @@ A *global object* is the object that provides access to built-in functions and v
|
|||
|
||||
In a browser it is named "window", for Node.JS it is "global", for other environments it may have another name.
|
||||
|
||||
So we can call `alert` two ways:
|
||||
For instance, we can call `alert` directly or as a method of `window`:
|
||||
|
||||
```js run
|
||||
alert("Hello");
|
||||
|
@ -529,78 +608,141 @@ alert("Hello");
|
|||
window.alert("Hello");
|
||||
```
|
||||
|
||||
Also we can access `Math` as `window.Math`:
|
||||
And the same applies to other built-ins. E.g. we can use `window.Array` instead of `Array`.
|
||||
|
||||
```js run
|
||||
alert( window.Math.min(5,1,4) ); // 1
|
||||
```
|
||||
The global object also carries global Function Declarations and `var` variables. We can read them and write using its properties, for instance:
|
||||
|
||||
Normally no one does so. Using the global object is generally not a good thing, it's recommended to evade that.
|
||||
|
||||
**The global object is not a global Lexical Environment.**
|
||||
|
||||
For historical reasons it also gives access to global Function Declarations and `var` variables, but not `let/const` variables:
|
||||
|
||||
<!-- can't make runnable in eval, will not work -->
|
||||
```js
|
||||
<!-- no-strict to move variables out of eval -->
|
||||
```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 `<iframe>`. Every browser window has its own `window` object and global variables. Javascript allows windows that come from the same site (same protocol, host, port) to access variables from each other.
|
||||
|
||||
That use is a little bit beyound our scope for now, but it looks like:
|
||||
```html run
|
||||
<iframe src="/" id="iframe"></iframe>
|
||||
|
||||
<script>
|
||||
alert( innerWidth ); // get innerWidth property of the current window (browser only)
|
||||
alert( Array ); // get Array of the current window (javascript core builtin)
|
||||
|
||||
// when the iframe loads...
|
||||
iframe.onload = function() {
|
||||
// get width of the iframe window
|
||||
*!*
|
||||
alert( iframe.contentWindow.innerWidth );
|
||||
*/!*
|
||||
// get the builtin Array from the iframe window
|
||||
*!*
|
||||
alert( iframe.contentWindow.Array );
|
||||
*/!*
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
````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.
|
||||
|
||||
|
|
94
1-js/5-deeper/3-new-function/article.md
Normal file
94
1-js/5-deeper/3-new-function/article.md
Normal file
|
@ -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.
|
89
1-js/plan2.txt
Normal file
89
1-js/plan2.txt
Normal file
|
@ -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
|
BIN
figures.sketch
BIN
figures.sketch
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue