up
This commit is contained in:
parent
4e9f302982
commit
b6adf0b6aa
9 changed files with 51 additions and 106 deletions
|
@ -143,9 +143,9 @@ So, during the function call we have two Lexical Environments: the inner one (fo
|
|||
It has a single property: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`.
|
||||
- The outer Lexical Environment is the global Lexical Environment.
|
||||
|
||||
It has `phrase` and the function itself.
|
||||
It has `phrase` variable and the function itself.
|
||||
|
||||
The inner Lexical Environment has a reference to the outer one.
|
||||
The inner Lexical Environment has a reference to the `outer` one.
|
||||
|
||||
**When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.**
|
||||
|
||||
|
@ -160,9 +160,9 @@ Let's see how the search proceeds in our example:
|
|||
|
||||
Now we can give the answer to the first question from the beginning of the chapter.
|
||||
|
||||
**A function gets outer variables as they are now; it uses the most recent values.**
|
||||
**A function gets outer variables as they are now, it uses the most recent values.**
|
||||
|
||||
That's because of the described mechanism. Old variable values are not saved anywhere. When a function wants them, it takes the current values from its own or an outer Lexical Environment.
|
||||
Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one.
|
||||
|
||||
So the answer to the first question is `Pete`:
|
||||
|
||||
|
@ -309,7 +309,7 @@ alert( counter2() ); // 0 (independent)
|
|||
```
|
||||
|
||||
|
||||
Hopefully, the situation with outer variables is clear now. For most situations such understanding is enough. There are few details in the specification that we omitted for brevity. So in the next section we cover even more details, not to miss anything.
|
||||
Hopefully, the situation with outer variables is clear now. For most situations such understanding is enough. There are few details in the specification that we omitted for brevity. So in the next section we cover even more details.
|
||||
|
||||
## Environments in detail
|
||||
|
||||
|
@ -323,13 +323,15 @@ Please note the additional `[[Environment]]` property is covered here. We didn't
|
|||
|
||||
At that starting moment there is only `makeCounter` function, because it's a Function Declaration. It did not run yet.
|
||||
|
||||
**All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation.** We didn't talk about it yet, but that's how the function knows where it was made.
|
||||
**All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation.**
|
||||
|
||||
We didn't talk about it yet, that's how the function knows where it was made.
|
||||
|
||||
Here, `makeCounter` is created in the global Lexical Environment, so `[[Environment]]` keeps a reference to it.
|
||||
|
||||
In other words, a function is "imprinted" with a reference to the Lexical Environment where it was born. And `[[Environment]]` is the hidden function property that has that reference.
|
||||
|
||||
2. The code runs on, the new global variable `counter` is declared and for its value `makeCounter()` is called. Here's a snapshot of the moment when the execution is on the first line inside `makeCounter()`:
|
||||
2. The code runs on, the new global variable `counter` is declared and gets the result of `makeCounter()` call. Here's a snapshot of the moment when the execution is on the first line inside `makeCounter()`:
|
||||
|
||||

|
||||
|
||||
|
@ -337,7 +339,7 @@ Please note the additional `[[Environment]]` property is covered here. We didn't
|
|||
|
||||
As all Lexical Environments, it stores two things:
|
||||
1. An Environment Record with local variables. In our case `count` is the only local variable (appearing when the line with `let count` is executed).
|
||||
2. The outer lexical reference, which is set to `[[Environment]]` of the function. Here `[[Environment]]` of `makeCounter` references the global Lexical Environment.
|
||||
2. The outer lexical reference, which is set to the value of `[[Environment]]` of the function. Here `[[Environment]]` of `makeCounter` references the global Lexical Environment.
|
||||
|
||||
So, now we have two Lexical Environments: the first one is global, the second one is for the current `makeCounter` call, with the outer reference to global.
|
||||
|
||||
|
@ -357,13 +359,11 @@ Please note the additional `[[Environment]]` property is covered here. We didn't
|
|||
|
||||
That function has only one line: `return count++`, that will be executed when we run it.
|
||||
|
||||
5. When the `counter()` is called, an "empty" Lexical Environment is created for it. It has no local variables by itself. But the `[[Environment]]` of `counter` is used as the outer reference for it, so it has access to the variables of the former `makeCounter()` call where it was created:
|
||||
5. When `counter()` is called, a new Lexical Environment is created for the call. It's empty, as `counter` has no local variables by itself. But the `[[Environment]]` of `counter` is used as the `outer` reference for it, that provides has access to the variables of the former `makeCounter()` call where it was created:
|
||||
|
||||

|
||||
|
||||
Now if it accesses a variable, it first searches its own Lexical Environment (empty), then the Lexical Environment of the former `makeCounter()` call, then the global one.
|
||||
|
||||
When it looks for `count`, it finds it among the variables `makeCounter`, in the nearest outer Lexical Environment.
|
||||
Now when the call looks for `count` variable, it first searches its own Lexical Environment (empty), then the Lexical Environment of the outer `makeCounter()` call, where finds it.
|
||||
|
||||
Please note how memory management works here. Although `makeCounter()` call finished some time ago, its Lexical Environment was retained in memory, because there's a nested function with `[[Environment]]` referencing it.
|
||||
|
||||
|
@ -373,13 +373,11 @@ Please note the additional `[[Environment]]` property is covered here. We didn't
|
|||
|
||||

|
||||
|
||||
So we return to the previous step with the only change -- the new value of `count`. The following calls all do the same.
|
||||
|
||||
7. Next `counter()` invocations do the same.
|
||||
|
||||
The answer to the second question from the beginning of the chapter should now be obvious.
|
||||
|
||||
The `work()` function in the code below uses the `name` from the place of its origin through the outer lexical environment reference:
|
||||
The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference:
|
||||
|
||||

|
||||
|
||||
|
@ -430,7 +428,7 @@ For instance, after `if` finishes, the `alert` below won't see the `user`, hence
|
|||
|
||||
### For, while
|
||||
|
||||
For a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for`, then it's also local to that Lexical Environment:
|
||||
For a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for(let ...)`, then it's also in there:
|
||||
|
||||
```js run
|
||||
for (let i = 0; i < 10; i++) {
|
||||
|
@ -441,7 +439,7 @@ for (let i = 0; i < 10; i++) {
|
|||
alert(i); // Error, no such variable
|
||||
```
|
||||
|
||||
Please note: `let i` is visually outside of `{...}`. The `for` construct is somewhat special here: each iteration of the loop has its own Lexical Environment with the current `i` in it.
|
||||
Please note: `let i` is visually outside of `{...}`. The `for` construct is special here: each iteration of the loop has its own Lexical Environment with the current `i` in it.
|
||||
|
||||
Again, similarly to `if`, after the loop `i` is not visible.
|
||||
|
||||
|
@ -537,7 +535,7 @@ There exist other ways besides parentheses to tell JavaScript that we mean a Fun
|
|||
}();
|
||||
```
|
||||
|
||||
In all the above cases we declare a Function Expression and run it immediately.
|
||||
In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code.
|
||||
|
||||
## Garbage collection
|
||||
|
||||
|
@ -554,7 +552,7 @@ f();
|
|||
|
||||
Here two values are technically the properties of the Lexical Environment. But after `f()` finishes that Lexical Environment becomes unreachable, so it's deleted from the memory.
|
||||
|
||||
...But if there's a nested function that is still reachable after the end of `f`, then its `[[Environment]]` reference keeps the outer lexical environment alive as well:
|
||||
...But if there's a nested function that is still reachable after the end of `f`, then it has `[[Environment]]` property that references the outer lexical environment, so it's also reachable and alive:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
|
@ -570,7 +568,7 @@ function f() {
|
|||
let g = f(); // g is reachable, and keeps the outer lexical environment in memory
|
||||
```
|
||||
|
||||
Please note that if `f()` is called many times, and resulting functions are saved, then the corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
|
||||
Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
|
@ -579,9 +577,8 @@ function f() {
|
|||
return function() { alert(value); };
|
||||
}
|
||||
|
||||
// 3 functions in array, every one of them links to Lexical Environment (LE for short)
|
||||
// 3 functions in array, every one of them links to Lexical Environment
|
||||
// from the corresponding f() run
|
||||
// LE LE LE
|
||||
let arr = [f(), f(), f()];
|
||||
```
|
||||
|
||||
|
@ -608,7 +605,7 @@ g = null; // ...and now the memory is cleaned up
|
|||
|
||||
As we've seen, in theory while a function is alive, all outer variables are also retained.
|
||||
|
||||
But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it's easy to see that an outer variable is not used -- it is removed.
|
||||
But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it's obvious from the code that an outer variable is not used -- it is removed.
|
||||
|
||||
**An important side effect in V8 (Chrome, Opera) is that such variable will become unavailable in debugging.**
|
||||
|
||||
|
@ -621,7 +618,7 @@ function f() {
|
|||
let value = Math.random();
|
||||
|
||||
function g() {
|
||||
debugger; // in console: type alert( value ); No such variable!
|
||||
debugger; // in console: type alert(value); No such variable!
|
||||
}
|
||||
|
||||
return g;
|
||||
|
@ -642,7 +639,7 @@ function f() {
|
|||
let value = "the closest value";
|
||||
|
||||
function g() {
|
||||
debugger; // in console: type alert( value ); Surprise!
|
||||
debugger; // in console: type alert(value); Surprise!
|
||||
}
|
||||
|
||||
return g;
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
|
||||
# Global object
|
||||
|
||||
The global object provides variables and functions that are available anywhere. Mostly, the ones that are built into the language or the environment.
|
||||
The global object provides variables and functions that are available anywhere. By default, those that are built into the language or the environment.
|
||||
|
||||
In a browser it is named `window`, for Node.js it is `global`, for other environments it may have another name.
|
||||
|
||||
Recently, `globalThis` was added to the language, as a standartized name for a global object, that should be supported across all environments. In some browsers, namely non-Chromium Edge, `globalThis` is not yet supported, but can be easily polyfilled.
|
||||
|
||||
We'll use `window` here, assuming that our environment is a browser. If your script may run in other environments, it's better to use `globalThis` instead.
|
||||
|
||||
All properties of the global object can be accessed directly:
|
||||
|
||||
```js run
|
||||
alert("Hello");
|
||||
|
||||
// the same as
|
||||
// is the same as
|
||||
window.alert("Hello");
|
||||
```
|
||||
|
||||
In a browser, global functions and variables declared with `var` become the property of the global object:
|
||||
In a browser, global functions and variables declared with `var` (not `let/const`!) become the property of the global object:
|
||||
|
||||
```js run untrusted refresh
|
||||
var gVar = 5;
|
||||
|
@ -24,9 +25,9 @@ var gVar = 5;
|
|||
alert(window.gVar); // 5 (became a property of the global object)
|
||||
```
|
||||
|
||||
Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use JavaScript modules where such thing doesn't happen. We'll cover them later in the chapter [](info:modules).
|
||||
Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such thing doesn't happen.
|
||||
|
||||
Also, more modern variable declarations `let` and `const` do not exhibit such behavior at all:
|
||||
If we used `let` instead, such thing wouldn't happen:
|
||||
|
||||
```js run untrusted refresh
|
||||
let gLet = 5;
|
||||
|
@ -52,7 +53,7 @@ alert(currentUser.name); // John
|
|||
alert(window.currentUser.name); // John
|
||||
```
|
||||
|
||||
That said, using global variables is generally discouraged. There should be as few global variables as possible. The code design where a function gets "input" variables and produces certain "outcome" is clearer, less prone to errors and easier to test.
|
||||
That said, using global variables is generally discouraged. There should be as few global variables as possible. The code design where a function gets "input" variables and produces certain "outcome" is clearer, less prone to errors and easier to test than if it uses outer or global variables.
|
||||
|
||||
## Using for polyfills
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ importance: 5
|
|||
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()` should decrease the `count` by 1.
|
||||
- `counter.set(value)` should set the counter to `value`.
|
||||
- `counter.decrease()` should decrease the counter by 1.
|
||||
|
||||
See the sandbox code for the complete usage example.
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
# Function object, NFE
|
||||
|
||||
As we already know, functions in JavaScript are values.
|
||||
As we already know, a function in JavaScript is a value.
|
||||
|
||||
Every value in JavaScript has a type. What type is a function?
|
||||
|
||||
|
@ -12,7 +12,7 @@ A good way to imagine functions is as callable "action objects". We can not only
|
|||
|
||||
## The "name" property
|
||||
|
||||
Function objects contain a few useable properties.
|
||||
Function objects contain some useable properties.
|
||||
|
||||
For instance, a function's name is accessible as the "name" property:
|
||||
|
||||
|
@ -24,14 +24,14 @@ function sayHi() {
|
|||
alert(sayHi.name); // sayHi
|
||||
```
|
||||
|
||||
What's more funny, the name-assigning logic is smart. It also assigns the correct name to functions that are used in assignments:
|
||||
What's kind of funny, the name-assigning logic is smart. It also assigns the correct name to a function even it's created without one, and then immediately assigned:
|
||||
|
||||
```js run
|
||||
let sayHi = function() {
|
||||
alert("Hi");
|
||||
}
|
||||
};
|
||||
|
||||
alert(sayHi.name); // sayHi (works!)
|
||||
alert(sayHi.name); // sayHi (there's a name!)
|
||||
```
|
||||
|
||||
It also works if the assignment is done via a default value:
|
||||
|
@ -93,7 +93,7 @@ alert(many.length); // 2
|
|||
|
||||
Here we can see that rest parameters are not counted.
|
||||
|
||||
The `length` property is sometimes used for introspection in functions that operate on other functions.
|
||||
The `length` property is sometimes used for [introspection](https://en.wikipedia.org/wiki/Type_introspection) in functions that operate on other functions.
|
||||
|
||||
For instance, in the code below the `ask` function accepts a `question` to ask and an arbitrary number of `handler` functions to call.
|
||||
|
||||
|
@ -102,9 +102,9 @@ Once a user provides their answer, the function calls the handlers. We can pass
|
|||
- A zero-argument function, which is only called when the user gives a positive answer.
|
||||
- A function with arguments, which is called in either case and returns an answer.
|
||||
|
||||
The idea is that we have a simple, no-arguments handler syntax for positive cases (most frequent variant), but are able to provide universal handlers as well.
|
||||
To call `handler` the right way, we examine the `handler.length` property.
|
||||
|
||||
To call `handlers` the right way, we examine the `length` property:
|
||||
The idea is that we have a simple, no-arguments handler syntax for positive cases (most frequent variant), but are able to support universal handlers as well:
|
||||
|
||||
```js run
|
||||
function ask(question, ...handlers) {
|
||||
|
@ -241,7 +241,7 @@ let sayHi = function *!*func*/!*(who) {
|
|||
sayHi("John"); // Hello, John
|
||||
```
|
||||
|
||||
There are two special things about the name `func`:
|
||||
There are two special things about the name `func`, that are the reasons for it:
|
||||
|
||||
1. It allows the function to reference itself internally.
|
||||
2. It is not visible outside of the function.
|
||||
|
@ -347,6 +347,6 @@ If the function is declared as a Function Expression (not in the main code flow)
|
|||
|
||||
Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature.
|
||||
|
||||
They create a "main" function and attach many other "helper" functions to it. For instance, the [jquery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`. And then adds `_.clone`, `_.keyBy` and other properties to (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts.
|
||||
They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`. And then adds `_.clone`, `_.keyBy` and other properties to (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts.
|
||||
|
||||
So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.
|
||||
|
|
|
@ -46,14 +46,13 @@ It is used in very specific cases, like when we receive code from a server, or t
|
|||
|
||||
## Closure
|
||||
|
||||
Usually, a function remembers where it was born in the special property `[[Environment]]`. It references the Lexical Environment from where it's created.
|
||||
Usually, a function remembers where it was born in the special property `[[Environment]]`. It references the Lexical Environment from where it's created (we covered that in the chapter <info:closure>).
|
||||
|
||||
But when a function is created using `new Function`, its `[[Environment]]` references not the current Lexical Environment, but instead the global one.
|
||||
But when a function is created using `new Function`, its `[[Environment]]` is set to reference not the current Lexical Environment, but the global one.
|
||||
|
||||
So, such function doesn't have access to outer variables, only to the global ones.
|
||||
|
||||
```js run
|
||||
|
||||
function getFunc() {
|
||||
let value = "test";
|
||||
|
||||
|
@ -99,6 +98,8 @@ So if `new Function` had access to outer variables, it would be unable to find r
|
|||
|
||||
**If `new Function` had access to outer variables, it would have problems with minifiers.**
|
||||
|
||||
Besides, such code would be architecturally bad and prone to errors.
|
||||
|
||||
To pass something to a function, created as `new Function`, we should use its arguments.
|
||||
|
||||
## Summary
|
||||
|
@ -111,7 +112,7 @@ let func = new Function ([arg1, arg2, ...argN], functionBody);
|
|||
|
||||
For historical reasons, arguments can also be given as a comma-separated list.
|
||||
|
||||
These three lines mean the same:
|
||||
These three declarations mean the same:
|
||||
|
||||
```js
|
||||
new Function('a', 'b', 'return a + b'); // basic syntax
|
||||
|
@ -119,4 +120,4 @@ new Function('a,b', 'return a + b'); // comma-separated
|
|||
new Function('a , b', 'return a + b'); // comma-separated with spaces
|
||||
```
|
||||
|
||||
Functions created with `new Function`, have `[[Environment]]` referencing the global Lexical Environment, not the outer one. Hence, they cannot use outer variables. But that's actually good, because it saves us from errors. Passing parameters explicitly is a much better method architecturally and causes no problems with minifiers.
|
||||
Functions created with `new Function`, have `[[Environment]]` referencing the global Lexical Environment, not the outer one. Hence, they cannot use outer variables. But that's actually good, because it insures us from errors. Passing parameters explicitly is a much better method architecturally and causes no problems with minifiers.
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
|
||||
```js run
|
||||
let i = 0;
|
||||
|
||||
let start = Date.now();
|
||||
|
||||
let timer = setInterval(count);
|
||||
|
||||
function count() {
|
||||
|
||||
for(let j = 0; j < 1000000; j++) {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == 1000000000) {
|
||||
alert("Done in " + (Date.now() - start) + 'ms');
|
||||
clearInterval(timer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
|
@ -1,32 +0,0 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Rewrite setTimeout with setInterval
|
||||
|
||||
Here's the function that uses nested `setTimeout` to split a job into pieces.
|
||||
|
||||
Rewrite it to `setInterval`:
|
||||
|
||||
```js run
|
||||
let i = 0;
|
||||
|
||||
let start = Date.now();
|
||||
|
||||
function count() {
|
||||
|
||||
if (i == 1000000000) {
|
||||
alert("Done in " + (Date.now() - start) + 'ms');
|
||||
} else {
|
||||
setTimeout(count);
|
||||
}
|
||||
|
||||
// a piece of heavy job
|
||||
for(let j = 0; j < 1000000; j++) {
|
||||
i++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
count();
|
||||
```
|
|
@ -177,7 +177,7 @@ let timerId = setTimeout(function request() {
|
|||
|
||||
And if the functions that we're scheduling are CPU-hungry, then we can measure the time taken by the execution and plan the next call sooner or later.
|
||||
|
||||
**Recursive `setTimeout` guarantees a delay between the executions, `setInterval` -- does not.**
|
||||
**Recursive `setTimeout` allows to set the delay between the executions more precisely than `setInterval`.**
|
||||
|
||||
Let's compare two code fragments. The first one uses `setInterval`:
|
||||
|
||||
|
@ -222,7 +222,7 @@ And here is the picture for the recursive `setTimeout`:
|
|||
|
||||
That's because a new call is planned at the end of the previous one.
|
||||
|
||||
````smart header="Garbage collection"
|
||||
````smart header="Garbage collection and setInterval/setTimeout callback"
|
||||
When a function is passed in `setInterval/setTimeout`, an internal reference is created to it and saved in the scheduler. It prevents the function from being garbage collected, even if there are no other references to it.
|
||||
|
||||
```js
|
||||
|
@ -288,7 +288,7 @@ For server-side JavaScript, that limitation does not exist, and there exist othe
|
|||
|
||||
- Methods `setInterval(func, delay, ...args)` and `setTimeout(func, delay, ...args)` allow to run the `func` regularly/once after `delay` milliseconds.
|
||||
- To cancel the execution, we should call `clearInterval/clearTimeout` with the value returned by `setInterval/setTimeout`.
|
||||
- Nested `setTimeout` calls is a more flexible alternative to `setInterval`. Also they can guarantee the minimal time *between* the executions.
|
||||
- Nested `setTimeout` calls is a more flexible alternative to `setInterval`, allowing to set the time *between* executions more precisely.
|
||||
- Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current code is complete".
|
||||
- The browser limits the minimal delay for five or more nested call of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons.
|
||||
|
||||
|
|
BIN
figures.sketch
BIN
figures.sketch
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue