ok
This commit is contained in:
parent
185efef6cc
commit
bef864adaf
36 changed files with 1248 additions and 334 deletions
|
@ -10,7 +10,11 @@ function sayHi() {
|
|||
}
|
||||
```
|
||||
|
||||
There is another way of creating a function that is called *Function Expression*:
|
||||
There is another way of creating a function that is called *Function Expression*.
|
||||
|
||||
[cut]
|
||||
|
||||
It looks like this:
|
||||
|
||||
```js
|
||||
let sayHi = function() {
|
||||
|
@ -91,6 +95,68 @@ The answer is simple:
|
|||
- The Function Expression appears in the context of the statement: `let sayHi = value;`. It's not a code block. The semicolon `;` is recommended at the end of statements, no matter what is the `value`. So the semicolon here is not related to Function Expression itself in any way, it just terminates the statement.
|
||||
````
|
||||
|
||||
## Anonymous functions
|
||||
|
||||
Let's see an example where function expressions come really handy.
|
||||
|
||||
We'll write a function `ask(question, yes, no)` with three parameters:
|
||||
|
||||
`question`
|
||||
: Text of the question
|
||||
|
||||
`yes`
|
||||
: Function to run if the answer is "Yes"
|
||||
|
||||
`no`
|
||||
: Function to run if the answer is "No"
|
||||
|
||||
The function should ask the `question` and, depending on the user's agreement, call `yes()` or `no()`:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
function ask(question, yes, no) {
|
||||
if (confirm(question)) yes()
|
||||
else no();
|
||||
}
|
||||
*/!*
|
||||
|
||||
function showOk() {
|
||||
alert( "You agreed." );
|
||||
}
|
||||
|
||||
function showCancel() {
|
||||
alert( "You canceled the execution." );
|
||||
}
|
||||
|
||||
// usage
|
||||
ask("Do you agree?", showOk, showCancel);
|
||||
```
|
||||
|
||||
The code looks kind of too simple, right? Why would anyone need such `ask`?
|
||||
|
||||
...It turns out that in the browser such functions are very required, the minor difference is that they ask not with a simple `confirm`, but output a much richer looking question window. But that's another story.
|
||||
|
||||
Right now let's see how we can write the same much shorter:
|
||||
|
||||
```js run no-beautify
|
||||
function ask(question, yes, no) {
|
||||
if (confirm(question)) yes()
|
||||
else no();
|
||||
}
|
||||
|
||||
*!*
|
||||
ask(
|
||||
"Do you agree?",
|
||||
function() { alert("You agreed."); },
|
||||
function() { alert("You canceled the execution."); }
|
||||
);
|
||||
*/!*
|
||||
```
|
||||
|
||||
Here functions are declared right inside the `ask(...)` call. They have no name (anonymous) and are not accessible outside of `ask`, but that's just what we need.
|
||||
|
||||
Such code appears very naturally, it's in the spirit of Javascript.
|
||||
|
||||
|
||||
```smart header="A function is a value representing an \"action\""
|
||||
Regular values like strings or numbers represent the *data*.
|
||||
|
@ -365,7 +431,7 @@ let sum = (a, b) => { // the figure bracket opens a multiline function
|
|||
*!*
|
||||
return result; // if we use figure brackets, must use return
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
alert( sum(1, 2) ); // 3
|
||||
```
|
||||
|
@ -376,29 +442,6 @@ Here we praised arrow functions for shortness. But that's not all! Arrow functio
|
|||
As for now, we can already use them for one-line actions.
|
||||
```
|
||||
|
||||
## new Function
|
||||
|
||||
And the last syntax for the functions:
|
||||
```js
|
||||
let func = new Function('a, b', 'return a + b');
|
||||
```
|
||||
|
||||
The major difference is that it creates a function literally from a string, at run time. See, both arguments are strings. The first one lists the arguments, while the second one is the function body.
|
||||
|
||||
All previous declarations required us, programmers, to write the function code in the script.
|
||||
|
||||
But `new Function` allows to turn any string into a function, for example we can receive a new function from the server and then execute it:
|
||||
|
||||
```js
|
||||
let str = ... receive the code from the server dynamically ...
|
||||
|
||||
let func = new Function('', str);
|
||||
func();
|
||||
```
|
||||
|
||||
It is used in very specific cases, like when we receive the code from the server, or to dynamically compile a function from a template. The need for such uses arises at advanced stages of the development.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
[todo review]
|
||||
|
|
|
@ -191,6 +191,7 @@ In-depth understanding of these optimization is also possible, there's no magic,
|
|||
|
||||
The main things to know:
|
||||
|
||||
- Garbage collection is performed automatically. We cannot force or prevent it.
|
||||
- Objects are retained in memory while they are reachable.
|
||||
- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.
|
||||
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
|
||||
# Function object
|
||||
|
||||
Functions in Javascript are two-faced. From one side they are callable "actions". From the other side, they are objects.
|
||||
|
||||
We can add/remove properties to them, and they have some useful properties of their own.
|
||||
|
||||
## Properties "name" and "length"
|
||||
|
||||
For Function Declarations, it's obvious:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert("Hi");
|
||||
}
|
||||
|
||||
alert(sayHi.name); // sayHi
|
||||
```
|
||||
|
||||
But what's more funny, the "name"-assigning logic is smart. It also recognizes functions, assigned to variables or provided as object methods:
|
||||
|
||||
```js run
|
||||
let sayHi = function() {
|
||||
alert("Hi");
|
||||
}
|
||||
|
||||
alert(sayHi.name); // sayHi (works!)
|
||||
```
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
|
||||
sayHi() { // works
|
||||
// ...
|
||||
},
|
||||
|
||||
sayBye: function() { // works too
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
alert(user.sayHi.name); // sayHi
|
||||
alert(user.sayBye.name); // sayBye
|
||||
```
|
||||
|
||||
In other cases, the name is empty, like here:
|
||||
|
||||
```js
|
||||
let arr = [function() {}];
|
||||
|
||||
alert( arr[0].name ); // ""
|
||||
```
|
||||
|
||||
In practice, most functions do have a name. It's mostly used for debugging, or, sometimes,
|
||||
|
||||
## Builtin "name" and "length"
|
||||
|
||||
Function objects have
|
||||
|
||||
We already know that functions in Javascript are objects
|
||||
Every value in Javascript has the type. What type of value is a function?
|
||||
|
||||
In Javascript, a function is an object.
|
||||
|
||||
NOT ALL !!! (some inherit?)
|
||||
|
||||
For example, all functions have property `name` (function name) and `length` (number of arguments):
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert("Hi");
|
||||
}
|
||||
|
||||
alert( sayHi.name ); // sayHi
|
||||
alert( sayHi.length ); // 0
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Custom properties
|
||||
|
||||
Using a function as an object is nothing special.
|
||||
|
||||
Here we add the `counter` property to track the total calls count:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert("Hi");
|
||||
|
||||
*!*
|
||||
// let's count how many times we run
|
||||
sayHi.counter++;
|
||||
*/!*
|
||||
}
|
||||
sayHi.counter = 0; // initial value
|
||||
|
||||
sayHi(); // Hi
|
||||
sayHi(); // Hi
|
||||
|
||||
alert( `Called ${sayHi.counter} times` ); // Called 2 times
|
||||
```
|
||||
|
||||
|
||||
```warn header="A property is not a variable"
|
||||
A property assigned to a function like `sayHi.counter = 0` does *not* define a local variable `counter` inside it. In other words, a property `sayHi.counter` and `let counter` inside the function (if we have it) are two unrelated things.
|
||||
|
||||
We can treat a function as an object for convenience, store properties in it, that has no effect on its execution.
|
||||
```
|
||||
|
||||
There are many well-known Javascript libraries that make a great use of custom function properties.
|
||||
|
||||
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).
|
||||
|
||||
So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
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.
|
|
@ -98,7 +98,7 @@ 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.
|
||||
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.
|
||||
We copy `let j = i`. This makes a loop body local `j` and copies the value of `i` to it. Primitives are copied "by value", so we actually get a complete independent copy of `i`, belonging to the current loop iteration.
|
||||
|
||||
|
|
|
@ -14,9 +14,8 @@ function makeArmy() {
|
|||
|
||||
let i = 0;
|
||||
while (i < 10) {
|
||||
let j = i;
|
||||
let shooter = function() { // shooter function
|
||||
alert( j ); // should show its number
|
||||
alert( i ); // should show its number
|
||||
};
|
||||
shooters.push(shooter);
|
||||
i++;
|
||||
|
|
|
@ -314,65 +314,6 @@ 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.
|
||||
```
|
||||
|
||||
### 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. Such variables are sometimes called *private* (to the function).
|
||||
|
||||
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, IIFE
|
||||
|
||||
A code block has it's own Lexical Environment and hence local variables.
|
||||
|
@ -456,16 +397,18 @@ function() { // <-- JavaScript assumes it is a Function Declarations, but no nam
|
|||
}();
|
||||
```
|
||||
|
||||
...And we can't actually use Function Declaration here, because Javascript does not allow them to be called immediately:f
|
||||
...But we can't use Function Declaration here, because Javascript does not allow Function Declarations to be called immediately:
|
||||
|
||||
```js run
|
||||
// syntax error
|
||||
// syntax error because of brackets below
|
||||
function go() {
|
||||
|
||||
}();
|
||||
}(); // <-- okay for Function Expressions, error for Function Declarations
|
||||
```
|
||||
|
||||
So the brackets are needed to show Javascript that we make a function in the context of another expression. Other means to do that:
|
||||
So the brackets are needed to show Javascript that the function is created in the context of another expression, and hence it's Function Declaration.
|
||||
|
||||
There are other ways to do that:
|
||||
|
||||
```js run
|
||||
!function() {
|
||||
|
@ -478,10 +421,6 @@ So the brackets are needed to show Javascript that we make a function in the con
|
|||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## The old "var"
|
||||
|
||||
In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration:
|
||||
|
@ -604,7 +543,7 @@ alert(phrase); // Error, phrase is not defined
|
|||
|
||||
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**.
|
||||
**The pitfall is that declarations are hoisted, but assignments are not.**
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -622,7 +561,7 @@ alert(phrase); // Error, phrase is not defined
|
|||
|
||||
The line `var phrase = "Hello"` has two actions in it: variable declaration `var` and assignment `=`.
|
||||
|
||||
The declaration is hoisted, but the assignment is not. So the code works essentially as this:
|
||||
The declaration is processed at the start of function execution (hoisted), but the assignment is not. So the code works essentially like this:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
|
@ -640,7 +579,7 @@ alert(phrase); // Error, phrase is not defined
|
|||
sayHi();
|
||||
```
|
||||
|
||||
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 `alert` runs without an error, because the variable `phrase` 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.
|
||||
|
@ -775,7 +714,6 @@ In-browser `window` is sometimes used for following purposes:
|
|||
</script>
|
||||
```
|
||||
|
||||
````smart header="Window and \"this\""
|
||||
As we know, usually `this` is used inside an object method to access the object. But there are special cases when `this` equals `window`:
|
||||
|
||||
1. The value of the global `this` is `window`:
|
||||
|
@ -785,7 +723,9 @@ As we know, usually `this` is used inside an object method to access the object.
|
|||
alert( this === window ); // true
|
||||
```
|
||||
|
||||
2. When a function with `this` is called in not-strict mode:
|
||||
In non-browser environments, it can be any other object, the specification does not say anything more exact.
|
||||
|
||||
2. When a function with `this` is called in not-strict mode (forgot `use strict`?):
|
||||
```js run no-strict
|
||||
// not in strict mode (!)
|
||||
function f() {
|
||||
|
@ -795,6 +735,5 @@ As we know, usually `this` is used inside an object method to access the object.
|
|||
f(); // called without an object
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
By specification, `this` in this case must be the global object. In non-browser environments it usually has another name, for instance, in Node.JS it is called `global`.
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
The solution uses `count` in the local variable, but addition methods are written right into the `counter`. They share the same outer lexical environment and also can access the current `count`.
|
|
@ -10,4 +10,6 @@ Modify the code of `makeCounter()` so that the counter can also decrease and set
|
|||
- `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.
|
||||
See the sandbox code for the complete usage example.
|
||||
|
||||
P.S. You can use either a closure or the function property to keep the current count. Or write both variants.
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
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`.
|
||||
3. According to the task, the function must become the number when used in `==`. Functions are objects, so the conversion happens as described in the chapter <info:object-toprimitive>, and we can provide our own method that returns the number.
|
||||
|
||||
Now the code:
|
||||
|
||||
|
@ -52,5 +52,5 @@ function f(b) {
|
|||
}
|
||||
```
|
||||
|
||||
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`.
|
||||
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`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion.
|
||||
|
|
@ -14,3 +14,4 @@ sum(6)(-1)(-2)(-3) == 0
|
|||
sum(0)(1)(2)(3)(4)(5) == 15
|
||||
```
|
||||
|
||||
P.S. Hint: you may need to setup custom object to primitive conversion for your function.
|
318
1-js/7-deeper/3-function-object/article.md
Normal file
318
1-js/7-deeper/3-function-object/article.md
Normal file
|
@ -0,0 +1,318 @@
|
|||
|
||||
# Function object, NFE
|
||||
|
||||
As we already know, functions in Javascript are values.
|
||||
|
||||
Every value in Javascript has the type. What type of value is a function?
|
||||
|
||||
In Javascript, a function is an object.
|
||||
|
||||
From one side, they are callable "actions". From the other side, we can treat them as objects: add/remove properties, pass by reference etc.
|
||||
|
||||
|
||||
## The "name" property
|
||||
|
||||
A function name is accessible as the "name" property.
|
||||
|
||||
For Function Declarations, it's obvious:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert("Hi");
|
||||
}
|
||||
|
||||
alert(sayHi.name); // sayHi
|
||||
```
|
||||
|
||||
But what's more funny, the name-assigning logic is smart. It also sticks the right name to function that are used in assignments:
|
||||
|
||||
```js run
|
||||
let sayHi = function() {
|
||||
alert("Hi");
|
||||
}
|
||||
|
||||
alert(sayHi.name); // sayHi (works!)
|
||||
```
|
||||
|
||||
Also here, as a default parameter:
|
||||
|
||||
```js run
|
||||
function f(sayHi = function() {}) {
|
||||
alert(sayHi.name); // sayHi (works!)
|
||||
}
|
||||
|
||||
f();
|
||||
```
|
||||
|
||||
In the specification, this is called a "contextual name". The function itself does not provide one, but in an assignment it's easy to figure out from context.
|
||||
|
||||
Object methods have names too:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
|
||||
sayHi() {
|
||||
// ...
|
||||
},
|
||||
|
||||
sayBye: function() {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
alert(user.sayHi.name); // sayHi
|
||||
alert(user.sayBye.name); // sayBye
|
||||
```
|
||||
|
||||
There's no magic though. In other cases, when there's no way to figure out the right name, it's empty, like here:
|
||||
|
||||
```js
|
||||
// function created inside array
|
||||
let arr = [function() {}];
|
||||
|
||||
alert( arr[0].name ); // <empty string>
|
||||
// the engine has no way to set up the right name, so there is none
|
||||
```
|
||||
|
||||
In practice, most functions do have a name. It's mostly used for debugging and checking.
|
||||
|
||||
## The "length" property
|
||||
|
||||
There is another built-in property "length" that returns the number of function parameters, for instance:
|
||||
|
||||
```js run
|
||||
function f1(a) {}
|
||||
function f2(a, b) {}
|
||||
function many(a, b, ...more) {}
|
||||
|
||||
alert(f1.length); // 1
|
||||
alert(f2.length); // 2
|
||||
alert(many.length); // 2
|
||||
```
|
||||
|
||||
Here we can see that rest parameters are not counted.
|
||||
|
||||
The `length` property is sometimes used for introspection: what kind of function we have?
|
||||
|
||||
For instance, in the code below `ask` function accepts `question` to ask and an arbitrary number of `handler` functions to call.
|
||||
|
||||
Each handler may be either a zero-argument function, then it is only called for a positive answer, or a single(or more)-argument function, then it is called with any answer as an argument.
|
||||
|
||||
```js run
|
||||
function ask(question, ...handlers) {
|
||||
let isYes = confirm(question);
|
||||
|
||||
for(let handler of handlers) {
|
||||
if (handler.length == 0) {
|
||||
if (isYes) handler();
|
||||
} else {
|
||||
handler(isYes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// for positive answer, both handlers are called
|
||||
// for negative answer, only the second one
|
||||
ask("Question?", () => alert('You said yes'), result => alert(result));
|
||||
```
|
||||
|
||||
This is a particular case of [polymorphism](https://en.wikipedia.org/wiki/Polymorphism_(computer_science)) -- treating arguments differently depending on their type -- or, in our case of their `length`.
|
||||
|
||||
## Custom properties
|
||||
|
||||
We can also add properties of our own.
|
||||
|
||||
Here we add the `counter` property to track the total calls count:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert("Hi");
|
||||
|
||||
*!*
|
||||
// let's count how many times we run
|
||||
sayHi.counter++;
|
||||
*/!*
|
||||
}
|
||||
sayHi.counter = 0; // initial value
|
||||
|
||||
sayHi(); // Hi
|
||||
sayHi(); // Hi
|
||||
|
||||
alert( `Called ${sayHi.counter} times` ); // Called 2 times
|
||||
```
|
||||
|
||||
```warn header="A property is not a variable"
|
||||
A property assigned to a function like `sayHi.counter = 0` does *not* define a local variable `counter` inside it. In other words, a property `counter` and a variable `let counter` are two unrelated things.
|
||||
|
||||
We can treat a function as an object, store properties in it, but that has no effect on its execution. Variables never use function properties and vise versa. These are just parallel words.
|
||||
```
|
||||
|
||||
Also we can rewrite the counter example from the chapter <info:closure> to use function property instead of the closure:
|
||||
|
||||
```js run
|
||||
function makeCounter() {
|
||||
// instead of:
|
||||
// let count = 0
|
||||
|
||||
function counter() {
|
||||
return counter.count++;
|
||||
};
|
||||
|
||||
counter.count = 0;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
let counter = makeCounter();
|
||||
alert( counter() ); // 0
|
||||
alert( counter() ); // 1
|
||||
```
|
||||
|
||||
Unlike the closures, the `count` is now bound to the function directly, not to its outer Lexical Environment.
|
||||
|
||||
Is it worse or better than using the closure?
|
||||
|
||||
The main difference is that if the value of `count` lives in an outer 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 hence the closure is more often used.
|
||||
|
||||
## Named Function Expression
|
||||
|
||||
Compare these two function definitions:
|
||||
|
||||
```js
|
||||
let sayHi = function() { // (1)
|
||||
alert('Hello');
|
||||
};
|
||||
|
||||
let sayHi = function *!*func*/!*() { // (2)
|
||||
alert('Hello');
|
||||
};
|
||||
```
|
||||
|
||||
Both create a function and put it into the variable `sayHi`. And usually we use the first variant is fine.
|
||||
|
||||
But if we specify a name right in the Function Expression (2), then it becomes an "internal function name", only visible from inside the function.
|
||||
|
||||
Let's see why we may need it.
|
||||
|
||||
As we've seen it's easy to copy a function and maybe replace the previous value with something else:
|
||||
|
||||
```js run
|
||||
let sayHi = function() {
|
||||
alert('Hello');
|
||||
};
|
||||
|
||||
// oh maybe another word is better? replace it!
|
||||
let oldSayHi = sayHi; // keep the old variant here
|
||||
sayHi = function() { // replace with a newer one
|
||||
alert("What's up dude?");
|
||||
};
|
||||
|
||||
|
||||
oldSayHi(); // Hello
|
||||
sayHi(); // What's up dude?
|
||||
```
|
||||
|
||||
The problem may occur if a function references *itself* from inside. It happens when the function wants to access its properties (`sayHi.counter` in the example above), or it wants to recursively call itself one more time.
|
||||
|
||||
But if the function has moved, then the old name becomes irrelevant! There will be an error.
|
||||
|
||||
Here's the code:
|
||||
|
||||
```js run
|
||||
// create a function
|
||||
let sayHi = function() {
|
||||
sayHi.counter++;
|
||||
alert('Hi ' + sayHi.counter);
|
||||
};
|
||||
sayHi.counter = 0;
|
||||
|
||||
// move it
|
||||
let movedSayHi = sayHi;
|
||||
|
||||
// overwrite the old name to make things more obvious
|
||||
sayHi = null;
|
||||
|
||||
*!*
|
||||
movedSayHi(); // Error: Cannot read property 'counter' of null
|
||||
*/!*
|
||||
```
|
||||
|
||||
The optional name which we can put into the Function Expression is exactly meant to solve this kind of problems.
|
||||
|
||||
- It is only visible from inside the function.
|
||||
- It always references the current function.
|
||||
|
||||
Let's use it to fix the code:
|
||||
|
||||
```js run
|
||||
// now with the internal name "say"
|
||||
let sayHi = function *!*say*/!*() {
|
||||
*!*say*/!*.counter++;
|
||||
alert('Hi ' + *!*say*/!*.counter); // and use it everywhere inside
|
||||
};
|
||||
sayHi.counter = 0;
|
||||
|
||||
let movedSayHi = sayHi;
|
||||
|
||||
sayHi = null;
|
||||
|
||||
movedSayHi(); // Hi 1
|
||||
movedSayHi(); // Hi 2 (works)
|
||||
|
||||
alert(say); // Error (say is undefined, that's normal)
|
||||
```
|
||||
|
||||
Please note that:
|
||||
|
||||
- The name `say` exists only inside the function. The last line demonstrates that.
|
||||
- The name `say` inside the function is always the current function, no matter in which variable it is. That's why it works.
|
||||
|
||||
So the outer code has it's variable `sayHi` or `movedSayHi` later to call the function. The `say` is an "internal function name", how it calls itself privately.
|
||||
|
||||
A Function Expression with a name is called *Named Function Expression*, often abbreviated as NFE.
|
||||
|
||||
The "internal name" feature described here is only available for Function Expressions, not to Function Declarations. For Function Declarations, there's just no syntax possibility to add a one more "internal" name for them.
|
||||
|
||||
Sometimes, when we need a reliable internal name, it's the reason to rewrite a Function Declaration to Named Function Expression form.
|
||||
|
||||
## Summary
|
||||
|
||||
Functions are objects.
|
||||
|
||||
Here we covered their properties:
|
||||
|
||||
- `name` -- the function name. Exists not only when given in the function definition, but also for assignments and object properties.
|
||||
- `length` -- the number of arguments in the function definition. Rest parameters are not counted.
|
||||
|
||||
If the function is declared as a Function Expression (not in the main code flow), and it carries the name, then it is called Named Function Expression. The name can be used inside to reference itself, for recursive calls or such.
|
||||
|
||||
Also, functions may carry additional properties. Many well-known Javascript libraries make a 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 less pollute the global space, so that a single library gives only one global variable. That lowers the chance of possible naming conflicts.
|
||||
|
||||
So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
|
||||
# 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.
|
139
1-js/7-deeper/4-new-function/article.md
Normal file
139
1-js/7-deeper/4-new-function/article.md
Normal file
|
@ -0,0 +1,139 @@
|
|||
|
||||
# The "new Function" syntax
|
||||
|
||||
There's one more way to create a function. It's rarely used, but sometimes there's no alternative.
|
||||
|
||||
[cut]
|
||||
|
||||
## The syntax
|
||||
|
||||
The syntax for creating a function:
|
||||
|
||||
```js
|
||||
let func = new Function('a', 'b', 'return a + b');
|
||||
```
|
||||
|
||||
All arguments of `new Function` are strings. Parameters go first, and the body is the last.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let sum = new Function('arg1', 'arg2', 'return arg1 + arg2');
|
||||
|
||||
alert( sum(1, 2) ); // 3
|
||||
```
|
||||
|
||||
If there are no arguments, then there will be only body:
|
||||
|
||||
```js run
|
||||
let sayHi = new Function('alert("Hello")');
|
||||
|
||||
sayHi(); // Hello
|
||||
```
|
||||
|
||||
The major difference from other ways we've seen -- the function is created literally from a string, that is passed at run time.
|
||||
|
||||
All previous declarations required us, programmers, to write the function code in the script.
|
||||
|
||||
But `new Function` allows to turn any string into a function, for example we can receive a new function from the server and then execute it:
|
||||
|
||||
```js
|
||||
let str = ... receive the code from the server dynamically ...
|
||||
|
||||
let func = new Function(str);
|
||||
func();
|
||||
```
|
||||
|
||||
It is used in very specific cases, like when we receive the code from the server, or to dynamically compile a function from a template. The need for that usually arises at advanced stages of development.
|
||||
|
||||
## The closure
|
||||
|
||||
Usually, a function remembers where it was born in the special property `[[Environment]] which references the current Lexical Environment.
|
||||
|
||||
But when a function is created using `new Function`, its `[[Environment]]` references not the current Lexical Environment, got the global one.
|
||||
|
||||
```js run
|
||||
|
||||
function getFunc() {
|
||||
let value = "test";
|
||||
|
||||
*!*
|
||||
let func = new Function('alert(value)');
|
||||
*/!*
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
getFunc()(); // error: value is not defined
|
||||
```
|
||||
|
||||
Compare it with the regular behavior:
|
||||
|
||||
```js run
|
||||
function getFunc() {
|
||||
let value = "test";
|
||||
|
||||
*!*
|
||||
let func = function() { alert(value); };
|
||||
*/!*
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
getFunc()(); // *!*"test"*/!*, from the Lexical Environment of getFunc
|
||||
```
|
||||
|
||||
This special feature 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 process of execution. We may receive it from the server or from another source.
|
||||
|
||||
Our new function needs to interact with the main script.
|
||||
|
||||
Maybe we want it to be able to access outer local variables?
|
||||
|
||||
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.
|
||||
|
||||
For instance, if a function has `let userName`, 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, so that's ok.
|
||||
|
||||
...But 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.
|
||||
|
||||
And it enforces better code. If we need to pass something to a function, created by `new Function`, we should pass it explicitly as arguments.
|
||||
|
||||
The "sum" function actually does that right:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
let sum = new Function('a', 'b', ' return a + b; ');
|
||||
*/!*
|
||||
|
||||
let a = 1, b = 2;
|
||||
|
||||
*!*
|
||||
// outer values are passed as arguments
|
||||
alert( sum(a, b) ); // 3
|
||||
*/!*
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
The syntax:
|
||||
|
||||
```js
|
||||
let func = new Function(arg1, arg2, ..., body);
|
||||
```
|
||||
|
||||
For historical reasons, arguments can also be given as a comma-separated list.
|
||||
|
||||
These three mean the same:
|
||||
|
||||
```js
|
||||
new Function('a', 'b', ' return a + b; '); // basic syntax
|
||||
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 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.
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
|
||||
```js run
|
||||
function printNumbersInterval() {
|
||||
var i = 1;
|
||||
var timerId = setInterval(function() {
|
||||
console.log(i);
|
||||
if (i == 20) clearInterval(timerId);
|
||||
i++;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// вызов
|
||||
printNumbersInterval();
|
||||
```
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Вывод чисел каждые 100 мс
|
||||
|
||||
Напишите функцию `printNumbersInterval()`, которая последовательно выводит в консоль числа от 1 до 20, с интервалом между числами 100 мс. То есть, весь вывод должен занимать 2000 мс, в течение которых каждые 100 мс в консоли появляется очередное число.
|
||||
|
||||
Нажмите на кнопку, открыв консоль, для демонстрации:
|
||||
<script>
|
||||
function printNumbersInterval() {
|
||||
var i = 1;
|
||||
var timerId = setInterval(function() {
|
||||
console.log(i);
|
||||
if (i == 20) clearInterval(timerId);
|
||||
i++;
|
||||
}, 100);
|
||||
}
|
||||
</script>
|
||||
<button onclick="printNumbersInterval()">printNumbersInterval()</button>
|
||||
</script>
|
||||
|
||||
P.S. Функция должна использовать `setInterval`.
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
|
||||
```js run
|
||||
function printNumbersTimeout20_100() {
|
||||
var i = 1;
|
||||
var timerId = setTimeout(function go() {
|
||||
console.log(i);
|
||||
if (i < 20) setTimeout(go, 100);
|
||||
i++;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// вызов
|
||||
printNumbersTimeout20_100();
|
||||
```
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Вывод чисел каждые 100 мс, через setTimeout
|
||||
|
||||
Сделайте то же самое, что в задаче <info:task/output-numbers-100ms>, но с использованием рекурсивного `setTimeout` вместо `setInterval`.
|
|
@ -0,0 +1,5 @@
|
|||
**Нужно выбрать вариант 2, который гарантирует браузеру свободное время между выполнениями `highlight`.**
|
||||
|
||||
Первый вариант может загрузить процессор на 100%, если `highlight` занимает время, близкое к 10 мс или, тем более, большее чем 10 мс, т.к. таймер не учитывает время выполнения функции.
|
||||
|
||||
Что интересно, в обоих случаях браузер не будет выводить предупреждение о том, что скрипт занимает много времени. Но от 100% загрузки процессора возможны притормаживания других операций. В общем, это совсем не то, что мы хотим, поэтому вариант 2.
|
|
@ -0,0 +1,30 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Для подсветки setInterval или setTimeout?
|
||||
|
||||
Стоит задача: реализовать подсветку синтаксиса в длинном коде при помощи JavaScript, для онлайн-редактора кода. Это требует сложных вычислений, особенно загружает процессор генерация дополнительных элементов страницы, визуально осуществляющих подсветку.
|
||||
|
||||
Поэтому решаем обрабатывать не весь код сразу, что привело бы к зависанию скрипта, а разбить работу на части: подсвечивать по 20 строк раз в 10 мс.
|
||||
|
||||
Как мы знаем, есть два варианта реализации такой подсветки:
|
||||
|
||||
1. Через `setInterval`, с остановкой по окончании работы:
|
||||
|
||||
```js
|
||||
timer = setInterval(function() {
|
||||
if (есть еще что подсветить) highlight();
|
||||
else clearInterval(timer);
|
||||
}, 10);
|
||||
```
|
||||
2. Через рекурсивный `setTimeout`:
|
||||
|
||||
```js
|
||||
setTimeout(function go() {
|
||||
highlight();
|
||||
if (есть еще что подсветить) setTimeout(go, 10);
|
||||
}, 10);
|
||||
```
|
||||
|
||||
Какой из них стоит использовать? Почему?
|
|
@ -0,0 +1,6 @@
|
|||
Ответы:
|
||||
|
||||
- `alert` выведет `100000000`.
|
||||
- **3**, срабатывание будет после окончания работы `hardWork`.
|
||||
|
||||
Так будет потому, что вызов планируется на `100 мс` от времени вызова `setTimeout`, но функция выполняется больше, чем `100 мс`, поэтому к моменту ее окончания время уже подошло и отложенный вызов выполняется тут же.
|
|
@ -0,0 +1,32 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Что выведет setTimeout?
|
||||
|
||||
В коде ниже запланирован запуск `setTimeout`, а затем запущена тяжёлая функция `hardWork`, выполнение которой занимает более долгое время, чем интервал до срабатывания таймера.
|
||||
|
||||
Когда сработает `setTimeout`? Выберите нужный вариант:
|
||||
|
||||
1. До выполнения `hardWork`.
|
||||
2. Во время выполнения `hardWork`.
|
||||
3. Сразу же по окончании `hardWork`.
|
||||
4. Через 100 мс после окончания `hardWork`.
|
||||
|
||||
Что выведет `alert` в коде ниже?
|
||||
|
||||
```js
|
||||
setTimeout(function() {
|
||||
alert( i );
|
||||
}, 100);
|
||||
|
||||
var i;
|
||||
|
||||
function hardWork() {
|
||||
// время выполнения этого кода >100 мс, сам код неважен
|
||||
for (i = 0; i < 1e8; i++) hardWork[i % 2] = i;
|
||||
}
|
||||
|
||||
hardWork();
|
||||
```
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
Вызов `alert(i)` в `setTimeout` введет `100000001`.
|
||||
|
||||
Можете проверить это запуском:
|
||||
|
||||
```js run
|
||||
var timer = setInterval(function() {
|
||||
i++;
|
||||
}, 10);
|
||||
|
||||
setTimeout(function() {
|
||||
clearInterval(timer);
|
||||
*!*
|
||||
alert( i ); // (*)
|
||||
*/!*
|
||||
}, 50);
|
||||
|
||||
var i;
|
||||
|
||||
function f() {
|
||||
// точное время выполнения не играет роли
|
||||
// здесь оно заведомо больше 100 мс
|
||||
for (i = 0; i < 1e8; i++) f[i % 2] = i;
|
||||
}
|
||||
|
||||
f();
|
||||
```
|
||||
|
||||
Правильный вариант срабатывания: **3** (сразу же по окончании `f` один раз).
|
||||
|
||||
Планирование `setInterval` будет вызывать функцию каждые `10 мс` после текущего времени. Но так как интерпретатор занят долгой функцией, то до конца ее работы никакого вызова не происходит.
|
||||
|
||||
За время выполнения `f` может пройти время, на которое запланированы несколько вызовов `setInterval`, но в этом случае остается только один, т.е. накопления вызовов не происходит. Такова логика работы `setInterval`.
|
||||
|
||||
После окончания текущего скрипта интерпретатор обращается к очереди запланированных вызовов, видит в ней `setInterval` и выполняет. А затем тут же выполняется `setTimeout`, очередь которого тут же подошла.
|
||||
|
||||
Итого, как раз и видим, что `setInterval` выполнился ровно 1 раз по окончании работы функции. Такое поведение кросс-браузерно.
|
|
@ -0,0 +1,45 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Что выведет после setInterval?
|
||||
|
||||
В коде ниже запускается `setInterval` каждые 10 мс, и через 50 мс запланирована его отмена.
|
||||
|
||||
После этого запущена тяжёлая функция `f`, выполнение которой (мы точно знаем) потребует более 100 мс.
|
||||
|
||||
Сработает ли `setInterval`, как и когда?
|
||||
|
||||
Варианты:
|
||||
|
||||
1. Да, несколько раз, *в процессе* выполнения `f`.
|
||||
2. Да, несколько раз, *сразу после* выполнения `f`.
|
||||
3. Да, один раз, *сразу после* выполнения `f`.
|
||||
4. Нет, не сработает.
|
||||
5. Может быть по-разному, как повезёт.
|
||||
|
||||
Что выведет `alert` в строке `(*)`?
|
||||
|
||||
```js
|
||||
var i;
|
||||
var timer = setInterval(function() { // планируем setInterval каждые 10 мс
|
||||
i++;
|
||||
}, 10);
|
||||
|
||||
setTimeout(function() { // через 50 мс - отмена setInterval
|
||||
clearInterval(timer);
|
||||
*!*
|
||||
alert( i ); // (*)
|
||||
*/!*
|
||||
}, 50);
|
||||
|
||||
// и запускаем тяжёлую функцию
|
||||
function f() {
|
||||
// точное время выполнения не играет роли
|
||||
// здесь оно заведомо больше 100 мс
|
||||
for (i = 0; i < 1e8; i++) f[i % 2] = i;
|
||||
}
|
||||
|
||||
f();
|
||||
```
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
Задача -- с небольшим "нюансом".
|
||||
|
||||
Есть браузеры, в которых на время работы JavaScript таймер "застывает", например таков IE. В них количество шагов будет почти одинаковым, +-1.
|
||||
|
||||
В других браузерах (Chrome) первый бегун будет быстрее.
|
||||
|
||||
Создадим реальные объекты `Runner` и запустим их для проверки:
|
||||
|
||||
```js run
|
||||
function Runner() {
|
||||
this.steps = 0;
|
||||
|
||||
this.step = function() {
|
||||
this.doSomethingHeavy();
|
||||
this.steps++;
|
||||
};
|
||||
|
||||
function fib(n) {
|
||||
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
|
||||
this.doSomethingHeavy = function() {
|
||||
for (var i = 0; i < 25; i++) {
|
||||
this[i] = fib(i);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
var runner1 = new Runner();
|
||||
var runner2 = new Runner();
|
||||
|
||||
// запускаем бегунов
|
||||
var t1 = setInterval(function() {
|
||||
runner1.step();
|
||||
}, 15);
|
||||
|
||||
var t2 = setTimeout(function go() {
|
||||
runner2.step();
|
||||
t2 = setTimeout(go, 15);
|
||||
}, 15);
|
||||
|
||||
// кто сделает больше шагов?
|
||||
setTimeout(function() {
|
||||
clearInterval(t1);
|
||||
clearTimeout(t2);
|
||||
alert( runner1.steps );
|
||||
alert( runner2.steps );
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
Если бы в шаге `step()` не было вызова `doSomethingHeavy()`, то есть он бы не требовал времени, то количество шагов было бы почти равным.
|
||||
|
||||
Но так как у нас шаг, всё же, что-то делает, и функция `doSomethingHeavy()` специально написана таким образом, что она требует (небольшого) времени, то первый бегун успеет сделать больше шагов. Ведь в `setTimeout` пауза `15` мс будет *между* шагами, а `setInterval` шагает равномерно, каждые `15` мс. Получается чаще.
|
|
@ -0,0 +1,37 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Кто быстрее?
|
||||
|
||||
Есть два бегуна:
|
||||
|
||||
```js
|
||||
var runner1 = new Runner();
|
||||
var runner2 = new Runner();
|
||||
```
|
||||
|
||||
У каждого есть метод `step()`, который делает шаг, увеличивая свойство `steps`.
|
||||
|
||||
Конкретный код метода `step()` не имеет значения, важно лишь что шаг делается не мгновенно, он требует небольшого времени.
|
||||
|
||||
Если запустить первого бегуна через `setInterval`, а второго -- через вложенный `setTimeout` -- какой сделает больше шагов за 5 секунд?
|
||||
|
||||
```js
|
||||
// первый?
|
||||
setInterval(function() {
|
||||
runner1.step();
|
||||
}, 15);
|
||||
|
||||
// или второй?
|
||||
setTimeout(function go() {
|
||||
runner2.step();
|
||||
setTimeout(go, 15);
|
||||
}, 15);
|
||||
|
||||
setTimeout(function() {
|
||||
alert( runner1.steps );
|
||||
alert( runner2.steps );
|
||||
}, 5000);
|
||||
```
|
||||
|
312
1-js/7-deeper/5-settimeout-setinterval/article.md
Normal file
312
1-js/7-deeper/5-settimeout-setinterval/article.md
Normal file
|
@ -0,0 +1,312 @@
|
|||
# Scheduling: setTimeout and setInterval [todo]
|
||||
|
||||
There are two methods to schedule function execution:
|
||||
|
||||
- `setTimeout` allows the function to run once after the given interval of time.
|
||||
- `setInterval` allows the function to run regularly.
|
||||
|
||||
These methods are not the part of Javascript specification. But most environments have internal scheduler and provide these methods. In particular, they are supported in all browsers and Node.JS.
|
||||
|
||||
|
||||
[cut]
|
||||
|
||||
## setTimeout
|
||||
|
||||
The syntax:
|
||||
|
||||
```js
|
||||
let timerId = setTimeout(func / code, delay[, arg1, arg2...])
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
`func/code`
|
||||
: Function or a string of code to execute.
|
||||
Usually, that's a function. For historical reasons, a string of code can be passed, but that's not recommended.
|
||||
|
||||
`delay`
|
||||
: The delay before run, in milliseconds (1000 ms = 1 second).
|
||||
|
||||
`arg1`, `arg2`...
|
||||
: Arguments to pass the function (not supported in IE9-)
|
||||
|
||||
|
||||
For instance, this code calls `sayHi()` after one second:
|
||||
|
||||
```js run
|
||||
function sayHi() {
|
||||
alert( 'Привет' );
|
||||
}
|
||||
|
||||
*!*
|
||||
setTimeout(sayHi, 1000);
|
||||
*/!*
|
||||
```
|
||||
|
||||
With arguments:
|
||||
|
||||
```js run
|
||||
function sayHi(phrase, who) {
|
||||
alert( phrase + ', ' + who );
|
||||
}
|
||||
|
||||
*!*
|
||||
setTimeout(sayHi, 1000, "John", "Hello"); // Hello, John
|
||||
*/!*
|
||||
```
|
||||
|
||||
If the first argument is a string, then Javascript creates a function from it.
|
||||
|
||||
So, this will also work:
|
||||
|
||||
```js run no-beautify
|
||||
setTimeout("alert('Hello')", 1000);
|
||||
```
|
||||
|
||||
But using strings is not recommended, use functions instead of them, like this:
|
||||
|
||||
```js run no-beautify
|
||||
setTimeout(() => alert('Hello'), 1000);
|
||||
```
|
||||
|
||||
````smart header="Pass a function, don't run it"
|
||||
Novice developers sometimes make a mistake by adding brackets `()` after the function:
|
||||
|
||||
```js
|
||||
// wrong!
|
||||
setTimeout(sayHi(), 1000);
|
||||
```
|
||||
That doesn't work, because `setTimeout` expects a reference to function. And here `sayHi()` runs the function, and the result of its execution is passed as the first argument. In our case the result of `sayHi()` is `undefined` (function returns nothing), so nothing is scheduled.
|
||||
````
|
||||
|
||||
### Canceling with clearTimeout
|
||||
|
||||
The call to `setTimeout` returns a "timer identifier" `timerId`, that we can use to cancel the execution.
|
||||
|
||||
The syntax to cancel:
|
||||
|
||||
```js
|
||||
let timerId = setTimeout(...);
|
||||
clearTimeout(timerId);
|
||||
```
|
||||
|
||||
In the code below we schedule the function, and then cancel it (changed our mind). As a result, nothing happens:
|
||||
|
||||
```js run no-beautify
|
||||
let timerId = setTimeout(() => alert("never happens"), 1000);
|
||||
alert(timerId); // timer identifier
|
||||
|
||||
clearTimeout(timerId);
|
||||
alert(timerId); // same identifier (doesn't become null after canceling)
|
||||
```
|
||||
|
||||
As we can see from `alert` output, in browsers timer identifier is a number. In other environments, that can be something else. For instance, Node.JS returns a timer object, with additional methods.
|
||||
|
||||
Again, there is no universal specification for these methods.
|
||||
|
||||
For browsers, timers are described in the [timers section](https://www.w3.org/TR/html5/webappapis.html#timers) of HTML5 standard.
|
||||
|
||||
## setInterval
|
||||
|
||||
Method `setInterval` has the same syntax as `setTimeout`:
|
||||
|
||||
```js
|
||||
let timerId = setInterval(func / code, delay[, arg1, arg2...])
|
||||
```
|
||||
|
||||
All arguments have the same meaning. But, unlike `setTimeout`, it runs the function not only once, but regularly with the given interval of time.
|
||||
|
||||
To stop the execution, we should call `clearInterval(timerId)`.
|
||||
|
||||
The following example will show the message every 2 seconds. After 5 seconds, the output is stopped:
|
||||
|
||||
```js run
|
||||
// repeat with the interval of 2 seconds
|
||||
let timerId = setInterval(() => alert('tick'), 2000);
|
||||
|
||||
// after 5 seconds stop
|
||||
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
|
||||
```
|
||||
|
||||
```smart header="Modal windows freeze time in Chrome/Opera/Safari"
|
||||
In browsers Chrome, Opera and Safari the internal timer is "frozen" while showing `alert/confirm/prompt`. And in IE or Firefox it continues ticking.
|
||||
|
||||
So if you run the code above and hold the `alert` window for a long time, then in Firefox/IE next `alert` will be shown at once (internal timer ticked up), and in Chrome/Opera/Safari -- after 2 seconds.
|
||||
```
|
||||
|
||||
## Recursive setTimeout
|
||||
|
||||
There are two ways of running something regularly.
|
||||
|
||||
One is `setInterval`. The other one is a recursive `setTimeout`:
|
||||
|
||||
```js
|
||||
/** instead of:
|
||||
let timerId = setInterval(() => alert('tick'), 2000);
|
||||
*/
|
||||
|
||||
let timerId = setTimeout(function tick() {
|
||||
alert('tick');
|
||||
*!*
|
||||
timerId = setTimeout(tick, 2000);
|
||||
*/!*
|
||||
}, 2000);
|
||||
```
|
||||
|
||||
The `setTimeout` above schedules next call right at the end of the previous one.
|
||||
|
||||
**Recursive `setTimeout` is more flexible method than `setInterval`: the next call may be planned differently, depending on the results of the current one.**
|
||||
|
||||
For instance, we have a service that each 5 seconds sends a request to server asking for data. In case if the server is overloaded, we can increase the interval to 10, 20, 60 seconds... And then return it back when everything stabilizes.
|
||||
|
||||
And if we regulary have CPU-hungry tasks, then we can measure the time taken by the execition and plan the next call sooner or later.
|
||||
|
||||
**Recursive `setTimeout` guarantees a delay before the executions, `setInterval` -- does not.**
|
||||
|
||||
Let's compare two code fragments. The first one uses `setInterval`:
|
||||
|
||||
```js
|
||||
let i = 1;
|
||||
setInterval(function() {
|
||||
func(i);
|
||||
}, 100);
|
||||
```
|
||||
|
||||
The second one uses recursive `setTimeout`:
|
||||
|
||||
```js
|
||||
let i = 1;
|
||||
setTimeout(function run() {
|
||||
func(i);
|
||||
setTimeout(run, 100);
|
||||
}, 100);
|
||||
```
|
||||
|
||||
For `setInterval` the internal scheduler will run `func(i)` every 100ms:
|
||||
|
||||

|
||||
|
||||
Did you notice?...
|
||||
|
||||
**The real delay between `func` calls for `setInterval` is less than in the code!**
|
||||
|
||||
That's natural, because the time taken by `func` execution "consumes" a part of the interval.
|
||||
|
||||
It is possible that `func` execution turns out to be longer than we expected and takes more than 100ms.
|
||||
|
||||
In this case the engine waits for `func` to complete, then checks the scheduler and if the time is up, then runs it again *immediately*.
|
||||
|
||||
As an edge case, if the function always takes longer to execute than `delay` argument, then the calls will happen without pause at all.
|
||||
|
||||
And this is the picture for recursive `setTimeout`:
|
||||
|
||||

|
||||
|
||||
**Recursive `setTimeout` guarantees fixed delay (here 100ms).**
|
||||
|
||||
That's because a new call is planned at the end of the previous one.
|
||||
|
||||
````smart header="Garbage collection"
|
||||
Garbage collector does not clean scheduled functions while they are actual.
|
||||
|
||||
When a function is passed in `setInterval/setTimeout`, an internal reference is created to it and saved in the scheduler. It prevents the function form being collected.
|
||||
|
||||
```js
|
||||
// the function will stay in memory until the scheduler calls it
|
||||
setTimeout(function() {}, 100);
|
||||
```
|
||||
|
||||
For `setTimeout` the reference is removed after a single execution. For `setInterval` -- only when `cancelInterval` is called.
|
||||
|
||||
Так как функция также тянет за собой всё замыкание, то ставшие неактуальными, но не отменённые `setInterval` могут приводить к излишним тратам памяти.
|
||||
````
|
||||
|
||||
## Минимальная задержка таймера
|
||||
|
||||
У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4 мс в современных браузерах. В более старых она может быть больше и достигать 15 мс.
|
||||
|
||||
По [стандарту](http://www.w3.org/TR/html5/webappapis.html#timers), минимальная задержка составляет 4 мс. Так что нет разницы между `setTimeout(..,1)` и `setTimeout(..,4)`.
|
||||
|
||||
Посмотреть минимальное разрешение "вживую" можно на следующем примере.
|
||||
|
||||
**В примере ниже каждая полоска удлиняется вызовом `setInterval` с указанной на ней задержкой -- от 0 мс (сверху) до 20 мс (внизу).**
|
||||
|
||||
Позапускайте его в различных браузерах. Вы заметите, что несколько первых полосок анимируются с одинаковой скоростью. Это как раз потому, что слишком маленькие задержки таймер не различает.
|
||||
|
||||
[iframe border="1" src="setinterval-anim" link edit]
|
||||
|
||||
```warn
|
||||
В Internet Explorer, нулевая задержка `setInterval(.., 0)` не сработает. Это касается именно `setInterval`, т.е. `setTimeout(.., 0)` работает нормально.
|
||||
```
|
||||
|
||||
```smart header="Откуда взялись эти 4 мс?"
|
||||
Почему минимальная задержка -- 4 мс, а не 1 мс? Зачем она вообще существует?
|
||||
|
||||
Это -- "привет" от прошлого. Браузер Chrome как-то пытался убрать минимальную задержку в своих ранних версиях, но оказалось, что существуют сайты, которые используют `setTimeout(..,0)` рекурсивно, создавая тем самым "асинхронный цикл". И, если задержку совсем убрать, то будет 100% загрузка процессора, такой сайт "подвесит" браузер.
|
||||
|
||||
Поэтому, чтобы не ломать существующие скрипты, решили сделать задержку. По возможности, небольшую. На время создания стандарта оптимальным числом показалось 4 мс.
|
||||
```
|
||||
|
||||
## Реальная частота срабатывания
|
||||
|
||||
В ряде ситуаций таймер будет срабатывать реже, чем обычно. Задержка между вызовами `setInterval(..., 4)` может быть не 4 мс, а 30 мс или даже 1000 мс.
|
||||
|
||||
- Большинство браузеров (десктопных в первую очередь) продолжают выполнять `setTimeout/setInterval`, даже если вкладка неактивна.
|
||||
|
||||
При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в "фоновой" вкладке будет срабатывать таймер, но редко.
|
||||
- При работе от батареи, в ноутбуке -- браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек.
|
||||
- При слишком большой загрузке процессора JavaScript может не успевать обрабатывать таймеры вовремя. При этом некоторые запуски `setInterval` будут пропущены.
|
||||
|
||||
**Вывод: на частоту 4 мс стоит ориентироваться, но не стоит рассчитывать.**
|
||||
|
||||
```online
|
||||
Посмотрим снижение частоты в действии на небольшом примере.
|
||||
|
||||
При клике на кнопку ниже запускается `setInterval(..., 90)`, который выводит список интервалов времени между 25 последними срабатываниями таймера. Запустите его. Перейдите на другую вкладку и вернитесь.
|
||||
|
||||
<div id="timer-interval-log"></div>
|
||||
|
||||
<button onclick="timerIntervalLog()">Запустить повтор с интервалом в 90 мс</button>
|
||||
<button onclick="clearInterval(timerIntervalLogTimer)">Остановить повтор</button>
|
||||
|
||||
<script>
|
||||
var timerIntervalLogTimer;
|
||||
function timerIntervalLog() {
|
||||
var arr = [];
|
||||
var d = new Date;
|
||||
timerIntervalLogTimer = setInterval(function() {
|
||||
var diff = new Date - d;
|
||||
if (diff > 100) diff = '<span style="color:red">'+diff+'</span>';
|
||||
arr.push( diff );
|
||||
if (arr.length > 25) arr.shift();
|
||||
document.getElementById('timer-interval-log').innerHTML = arr;
|
||||
d = new Date;
|
||||
}, 90);
|
||||
}
|
||||
</script>
|
||||
|
||||
Если ваш браузер увеличивает таймаут при фоновом выполнении вкладки, то вы увидите увеличенные интервалы, помеченные <span style="color:red">красным</span>.
|
||||
|
||||
Кроме того, вы заметите, что таймер не является идеально точным ;)
|
||||
```
|
||||
|
||||
## Разбивка долгих скриптов
|
||||
|
||||
Нулевой или небольшой таймаут также используют, чтобы разорвать поток выполнения "тяжелых" скриптов.
|
||||
|
||||
Например, скрипт для подсветки синтаксиса должен проанализировать код, создать много цветных элементов для подсветки и добавить их в документ -- на большом файле это займёт много времени, браузер может даже подвиснуть, что неприемлемо.
|
||||
|
||||
Для того, чтобы этого избежать, сложная задача разбивается на части, выполнение каждой части запускается через мини-интервал после предыдущей, чтобы дать браузеру время.
|
||||
|
||||
Например, осуществляется анализ и подсветка первых 100 строк, затем через 20 мс -- следующие 100 строк и так далее. При этом можно подстраиваться под CPU посетителя: замерять время на анализ 100 строк и, если процессор хороший, то в следующий раз обработать 200 строк, а если плохой -- то 50. В итоге подсветка будет работать с адекватной быстротой и без тормозов на любых текстах и компьютерах.
|
||||
|
||||
## Итого
|
||||
|
||||
- Методы `setInterval(func, delay)` и `setTimeout(func, delay)` позволяют запускать `func` регулярно/один раз через `delay` миллисекунд.
|
||||
- Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом `clearInterval/clearTimeout`.
|
||||
- В случаях, когда нужно гарантировать задержку между регулярными вызовами или гибко её менять, вместо `setInterval` используют рекурсивный `setTimeout`.
|
||||
- Минимальная задержка по стандарту составляет `4 мс`. Браузеры соблюдают этот стандарт, но некоторые другие среды для выполнения JS, например Node.JS, могут предоставить и меньше задержки.
|
||||
- В реальности срабатывания таймера могут быть гораздо реже, чем назначено, например если процессор перегружен, вкладка находится в фоновом режиме, ноутбук работает от батареи или по какой-то иной причине.
|
||||
|
||||
Браузерных особенностей почти нет, разве что вызов `setInterval(..., 0)` с нулевой задержкой в IE недопустим, нужно указывать `setInterval(..., 1)`.
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
div {
|
||||
height: 18px;
|
||||
margin: 1px;
|
||||
background-color: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<input type="button" id="start" value="Старт">
|
||||
<input type="button" id="stop" value="Стоп" disabled>
|
||||
|
||||
<script>
|
||||
for (var i = 0; i <= 20; i += 2) {
|
||||
document.write('<div>' + i + '</div>');
|
||||
}
|
||||
|
||||
var startButton = document.getElementById('start');
|
||||
var stopButton = document.getElementById('stop');
|
||||
|
||||
var timers = [];
|
||||
|
||||
stopButton.onclick = function() {
|
||||
startButton.disabled = false;
|
||||
stopButton.disabled = true;
|
||||
|
||||
for (var i = 0; i < timers.length; i++) clearInterval(timers[i]);
|
||||
timers = [];
|
||||
}
|
||||
|
||||
startButton.onclick = function() {
|
||||
startButton.disabled = true;
|
||||
stopButton.disabled = false;
|
||||
|
||||
var divs = document.getElementsByTagName('div');
|
||||
for (var i = 0; i < divs.length; i++) {
|
||||
animateDiv(divs, i);
|
||||
}
|
||||
}
|
||||
|
||||
function animateDiv(divs, i) {
|
||||
var div = divs[i],
|
||||
speed = div.innerHTML;
|
||||
timers[i] = setInterval(function() {
|
||||
div.style.width = (parseInt(div.style.width || 0) + 2) % 400 + 'px'
|
||||
}, speed);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
1-js/7-deeper/5-settimeout-setinterval/setinterval-interval.png
Normal file
BIN
1-js/7-deeper/5-settimeout-setinterval/setinterval-interval.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
1-js/7-deeper/5-settimeout-setinterval/settimeout-interval.png
Normal file
BIN
1-js/7-deeper/5-settimeout-setinterval/settimeout-interval.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -56,6 +56,10 @@ data-structures
|
|||
<<<
|
||||
<<< call/apply?
|
||||
|
||||
func.name + NFE - together
|
||||
closures need function objects
|
||||
NFE need recursion
|
||||
scheduling needs recursion, closures
|
||||
|
||||
recursion (
|
||||
running execution context = where + lexical environment = envrec + outer
|
||||
|
@ -65,12 +69,6 @@ recursion (
|
|||
task: traverse list back
|
||||
)
|
||||
|
||||
<< function name
|
||||
name property (obj props, methods, assignments - set it)
|
||||
length property
|
||||
|
||||
<< NFE
|
||||
|
||||
closures
|
||||
LE outer
|
||||
returning a function
|
||||
|
@ -78,19 +76,28 @@ closures
|
|||
new Function?
|
||||
counter object?
|
||||
|
||||
<< new function
|
||||
|
||||
<<< memory management of closures? (AFTER CLOSURES! Сюрприз нельзя получить!!!)
|
||||
|
||||
|
||||
function-object
|
||||
name property (obj props, methods, assignments - set it)
|
||||
length property
|
||||
custom properties
|
||||
|
||||
new function
|
||||
|
||||
scheduling: settimeout, setinterval
|
||||
recursive settimeout
|
||||
|
||||
<<< bind, fixing context
|
||||
|
||||
<<< call/apply, decorators
|
||||
|
||||
|
||||
bind, currying
|
||||
decorators
|
||||
constructors
|
||||
classes
|
||||
instanceof
|
||||
IIFE ?
|
||||
|
||||
<<< memory management? (AFTER CLOSURES! Сюрприз нельзя получить!!!)
|
||||
|
||||
|
||||
после 4-object сделать
|
||||
descriptors
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue