diff --git a/1-js/3-object-basics/1-object/article.md b/1-js/3-object-basics/1-object/article.md index 5b0d6872..404de663 100644 --- a/1-js/3-object-basics/1-object/article.md +++ b/1-js/3-object-basics/1-object/article.md @@ -235,22 +235,16 @@ let obj = { alert( obj.for + obj.let + obj.return ); // 6 ``` -Basically, any name is allowed, with one exclusion: `__proto__`. - -The built-in property named `__proto__` has a special functionality (we'll cover it later), and it can't be set to a non-object value: +Basically, any name is allowed, but there's a special one: `"__proto__"`. We can't set it to a non-object value: ```js run -let obj = {}; -obj.__proto__ = 5; +let obj = { __proto__: 5 }; alert(obj.__proto__); // [object Object], didn't work as intended ``` -As you we see from the code, an assignment to a primitive is ignored. If we want to store *arbitrary* (user-provided) keys, then such behavior can be the source of bugs and even vulnerabilities, because it's unexpected. There's another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in the chapter , which supports arbitrary keys. +Later we'll learn more about that `__proto__` and see how to work around it [todo Object.create(null)]. Also we'll learn another data structure [Map](info:map-set-weakmap-weakset) that doesn't have such problems and supports arbitrary keys. ```` - - - ## Existance check A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined: diff --git a/1-js/6-data-structures/3-string/article.md b/1-js/6-data-structures/3-string/article.md index 8fc5c4fa..d2040f16 100644 --- a/1-js/6-data-structures/3-string/article.md +++ b/1-js/6-data-structures/3-string/article.md @@ -451,7 +451,9 @@ Let's recap the methods to avoid any confusion: ```smart header="Which one to choose?" -All of them can do the job. The author finds himself using `slice` almost all the time. +All of them can do the job. Formally, `substr` has a minor drawback: it is described not in the core Javascript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere. + +The author finds himself using `slice` almost all the time. ``` ## Comparing strings @@ -526,7 +528,7 @@ The characters are compared by their numeric code. The greater code means that t ### Correct comparisons -The "right" algorithm to do string comparisons is more complex than it may seem. Because the alphabets are different for different languages. So the same letter may be located differently in different alphabets, that is -- even if it looks the same, different alphabets put it in different place. +The "right" algorithm to do string comparisons is more complex than it may seem. Because alphabets are different for different languages. The same-looking letter may be located differently in different alphabets. So, the browser needs to know the language to compare. diff --git a/1-js/7-deeper/2-closure/article.md b/1-js/7-deeper/2-closure/article.md index 28909e6f..750a0a85 100644 --- a/1-js/7-deeper/2-closure/article.md +++ b/1-js/7-deeper/2-closure/article.md @@ -420,6 +420,129 @@ There are other ways to do that: }(); ``` +## Garbage collection + +Lexical Environment objects that we've been talking about follow same garbage collection rules as regular ones. + +So, a Lexical Environment exists while there's a nested function referencing it with its `[[Environment]]`. + +- Usually, Lexical Environment is cleaned up after the function run. Even if it has a nested function, for instance: + + ```js + function f() { + let value = 123; + + function g() {} // g is local + } + + f(); + ``` + + Here both `value` and `g` become unreachable after the end of `f()`, and so even though `g` references its outer lexical environment, that doesn't matter. + +- But if `g` were returned and kept reachable, then that its reference keeps the outer lexical environment alive as well: + + ```js + function f() { + let value = 123; + + function g() {} + + *!* + return g; + */!* + } + + let g = f(); // function g will live and keep the outer lexical environment in memory + ``` + +- 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: + + ```js + function f() { + var value = Math.random(); + + return function() {}; + } + + // 3 functions in array, every of them links to LexicalEnvrironment + // from the corresponding f() run + let arr = [f(), f(), f()]; + ``` +- A Lexical Environment object lives until it is reachable. In the code below, after `g` becomes unreachable, it dies with it: + + ```js + function f() { + var value = 123; + + function g() {} + + return g; + } + + let g = f(); // while g is alive + // there corresponding Lexical Environment lives + + g = null; // ...and now the memory is cleaned up + ``` + +### Real-life optimizations + +As we've seen, in theory while a function is alive, all outer variabels are also retained. + +But in practice, JS 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. + +In the code above `value` is not used in `g`. So it will be cleaned up from the memory. + +**Important side effect in V8 (Chrome, Opera) is that such variable will become unavailable in debugging!** + +Try running the example below with the open Developer Tools in Chrome. + +When it pauses, in console type `alert(value)`. + +```js run +function f() { + var value = Math.random(); + + function g() { + debugger; // in console: type alert( value ); No such variable! + } + + return g; +} + +var g = f(); +g(); +``` + +As you could see -- there is no such variable! The engine decided that we won't need it and removed it. + +That may lead to funny (if not such time-consuming) debugging issues, especially when we can see instead of *our* variable the more outer one: + +```js run global +let value = "Surprise!"; + +function f() { + let value = "the closest value"; + + function g() { + debugger; // in console: type alert( value ); Surprise! + } + + return g; +} + +let g = f(); +g(); +``` + +```warn header="See ya!" +This feature of V8 is good to know. If you are debugging with Chrome/Opera, sooner or later you will meet it. + +That is not a bug of debugger, but a special feature of V8. Maybe it will be changed sometimes. +You always can check for it by running examples on this page. +``` + ## The old "var" @@ -586,7 +709,6 @@ The features described above make using `var` inconvenient most of time. First, - ## Global object A *global object* is the object that provides access to built-in functions and values, defined by the specification and the environment. diff --git a/1-js/7-deeper/3-function-object/article.md b/1-js/7-deeper/3-function-object/article.md index 155edded..bacc79d6 100644 --- a/1-js/7-deeper/3-function-object/article.md +++ b/1-js/7-deeper/3-function-object/article.md @@ -170,11 +170,11 @@ alert( counter() ); // 0 alert( counter() ); // 1 ``` -Unlike the closures, the `count` is now bound to the function directly, not to its outer Lexical Environment. +Unlike examples from chapter , the `count` is now stored in the function directly, not in 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: +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 nested functions may modify it. And if it's bound to function, then such thing is possible: ```js run function makeCounter() { @@ -196,108 +196,136 @@ 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. +So it depends on our aims which variant to choose. ## Named Function Expression -Compare these two function definitions: +Named Function Expression or, shortly, NFE, is a term a Function Expression that has a name. + +For instance, let's take an ordinary Function Expression: ```js -let sayHi = function() { // (1) - alert('Hello'); -}; - -let sayHi = function *!*func*/!*() { // (2) - alert('Hello'); +let sayHi = function(who) { + alert(`Hello, ${who}`); }; ``` -Both create a function and put it into the variable `sayHi`. And usually we use the first variant is fine. +...And add a name to it: -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'); +```js +let sayHi = function *!*func*/!*(who) { + alert(`Hello, ${who}`); }; - -// 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. +What's the role of that additional `"func"` name? -But if the function has moved, then the old name becomes irrelevant! There will be an error. +First let's note, that we still have a Function Expression. Adding the name `"func"` after `function` did not make it a Function Declaration, because it is still created as a part of an assignment expression. -Here's the code: +Adding such a name also did not break anything. + +The function is still available as `sayHi()`: + +```js run +let sayHi = function *!*func*/!*(who) { + alert(`Hello, ${who}`); +}; + +sayHi("John"); // Hello, John +``` + +There are two special things about the name `func`: + +1. It allows to reference the function from inside itself. +2. It is not visible outside of the function. + +For instance, the function `sayHi` below re-calls itself with `"Guest"` if no `who` is provided: ```js run -// create a function -let sayHi = function() { - sayHi.counter++; - alert('Hi ' + sayHi.counter); +let sayHi = function *!*func*/!*(who) { + if (who) { + alert(`Hello, ${who}`); + } else { +*!* + func("Guest"); // use func to re-call itself +*/!* + } }; -sayHi.counter = 0; -// move it -let movedSayHi = sayHi; +sayHi(); // Hello, Guest -// overwrite the old name to make things more obvious +// But this won't work: +func(); // Error, func is not defined (not visible outside of the function) +``` + +Why do we use `func`? Maybe just use `sayHi` for the nested call? + + +Actually, in most cases we can: + +```js +let sayHi = function(who) { + if (who) { + alert(`Hello, ${who}`); + } else { +*!* + sayHi("Guest"); +*/!* + } +}; +``` + +The problem with that code is that the value of `sayHi` may change. The function may go to another variable, and the code will start to give errors: + +```js run +let sayHi = function(who) { + if (who) { + alert(`Hello, ${who}`); + } else { +*!* + sayHi("Guest"); // Error: sayHi is not a function +*/!* + } +}; + +let welcome = sayHi; sayHi = null; -*!* -movedSayHi(); // Error: Cannot read property 'counter' of null -*/!* +welcome(); // Error, the nested sayHi call doesn't work any more! ``` -The optional name which we can put into the Function Expression is exactly meant to solve this kind of problems. +That happens because the function takes `sayHi` from its outer lexical environment. There's no local `sayHi`, so the outer variable is used. And at the moment of the call that outer `sayHi` is `null`. -- It is only visible from inside the function. -- It always references the current function. +The optional name which we can put into the Function Expression is exactly meant to solve this kind of problems. 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 +let sayHi = function *!*func*/!*(who) { + if (who) { + alert(`Hello, ${who}`); + } else { +*!* + func("Guest"); // Now all fine +*/!* + } }; -sayHi.counter = 0; - -let movedSayHi = sayHi; +let welcome = sayHi; sayHi = null; -movedSayHi(); // Hi 1 -movedSayHi(); // Hi 2 (works) - -alert(say); // Error (say is undefined, that's normal) +welcome(); // Hello, Guest (nested call works) ``` -Please note that: +Now it works, because the name `"func"` is function-local. It is not taken from outside (and not visible there). The specification guarantees that it always references the current function. -- 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 outer code still has it's variable `sayHi` or `welcome` later. And `func` is an "internal function name", how it calls itself privately. +```smart header="No such thing for Function Declaration" 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 diff --git a/1-js/7-deeper/5-settimeout-setinterval/1-output-numbers-100ms/solution.md b/1-js/7-deeper/5-settimeout-setinterval/1-output-numbers-100ms/solution.md index 590b9ac6..13f01deb 100644 --- a/1-js/7-deeper/5-settimeout-setinterval/1-output-numbers-100ms/solution.md +++ b/1-js/7-deeper/5-settimeout-setinterval/1-output-numbers-100ms/solution.md @@ -1,16 +1,42 @@ +Using `setInterval`: + +```js run +function printNumbers(from, to) { + let current = from; + + let timerId = setInterval(function() { + alert(current); + if (current == to) { + clearInterval(timerId); + } + current++; + }, 1000); +} + +// usage: +printNumbers(5, 10); +``` + +Using recursive `setTimeout`: + ```js run -function printNumbersInterval() { - var i = 1; - var timerId = setInterval(function() { - console.log(i); - if (i == 20) clearInterval(timerId); - i++; - }, 100); +function printNumbers(from, to) { + let current = from; + + setTimeout(function go() { + alert(current); + if (current < to) { + setTimeout(go, 1000); + } + current++; + }, 1000); } -// вызов -printNumbersInterval(); +// usage: +printNumbers(5, 10); ``` +Note that in both solutions, there is an initial delay before the first output. Sometimes we need to add a line to make the first output immediately, that's easy to do. + diff --git a/1-js/7-deeper/5-settimeout-setinterval/1-output-numbers-100ms/task.md b/1-js/7-deeper/5-settimeout-setinterval/1-output-numbers-100ms/task.md index 70887f57..bddd0934 100644 --- a/1-js/7-deeper/5-settimeout-setinterval/1-output-numbers-100ms/task.md +++ b/1-js/7-deeper/5-settimeout-setinterval/1-output-numbers-100ms/task.md @@ -2,22 +2,12 @@ importance: 5 --- -# Вывод чисел каждые 100 мс +# Output every second -Напишите функцию `printNumbersInterval()`, которая последовательно выводит в консоль числа от 1 до 20, с интервалом между числами 100 мс. То есть, весь вывод должен занимать 2000 мс, в течение которых каждые 100 мс в консоли появляется очередное число. +Write a function `printNumbers(from, to)` that outputs a number every second, starting from `from` and ending with `two`. -Нажмите на кнопку, открыв консоль, для демонстрации: - - - +Make two variants of the solution. + +1. Using `setInterval`. +2. Using recursive `setTimeout`. -P.S. Функция должна использовать `setInterval`. \ No newline at end of file diff --git a/1-js/7-deeper/5-settimeout-setinterval/2-output-numbers-100ms-settimeout/solution.md b/1-js/7-deeper/5-settimeout-setinterval/2-output-numbers-100ms-settimeout/solution.md deleted file mode 100644 index 272c9687..00000000 --- a/1-js/7-deeper/5-settimeout-setinterval/2-output-numbers-100ms-settimeout/solution.md +++ /dev/null @@ -1,16 +0,0 @@ - - -```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(); -``` - diff --git a/1-js/7-deeper/5-settimeout-setinterval/2-output-numbers-100ms-settimeout/task.md b/1-js/7-deeper/5-settimeout-setinterval/2-output-numbers-100ms-settimeout/task.md deleted file mode 100644 index c5bd1ba4..00000000 --- a/1-js/7-deeper/5-settimeout-setinterval/2-output-numbers-100ms-settimeout/task.md +++ /dev/null @@ -1,7 +0,0 @@ -importance: 5 - ---- - -# Вывод чисел каждые 100 мс, через setTimeout - -Сделайте то же самое, что в задаче , но с использованием рекурсивного `setTimeout` вместо `setInterval`. \ No newline at end of file diff --git a/1-js/7-deeper/5-settimeout-setinterval/3-highlight-tactics/solution.md b/1-js/7-deeper/5-settimeout-setinterval/3-highlight-tactics/solution.md deleted file mode 100644 index a9e6df88..00000000 --- a/1-js/7-deeper/5-settimeout-setinterval/3-highlight-tactics/solution.md +++ /dev/null @@ -1,5 +0,0 @@ -**Нужно выбрать вариант 2, который гарантирует браузеру свободное время между выполнениями `highlight`.** - -Первый вариант может загрузить процессор на 100%, если `highlight` занимает время, близкое к 10 мс или, тем более, большее чем 10 мс, т.к. таймер не учитывает время выполнения функции. - -Что интересно, в обоих случаях браузер не будет выводить предупреждение о том, что скрипт занимает много времени. Но от 100% загрузки процессора возможны притормаживания других операций. В общем, это совсем не то, что мы хотим, поэтому вариант 2. \ No newline at end of file diff --git a/1-js/7-deeper/5-settimeout-setinterval/3-highlight-tactics/task.md b/1-js/7-deeper/5-settimeout-setinterval/3-highlight-tactics/task.md deleted file mode 100644 index a6309aaf..00000000 --- a/1-js/7-deeper/5-settimeout-setinterval/3-highlight-tactics/task.md +++ /dev/null @@ -1,30 +0,0 @@ -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); - ``` - -Какой из них стоит использовать? Почему? \ No newline at end of file diff --git a/1-js/7-deeper/5-settimeout-setinterval/3-rewrite-settimeout-0/solution.md b/1-js/7-deeper/5-settimeout-setinterval/3-rewrite-settimeout-0/solution.md new file mode 100644 index 00000000..322c66a7 --- /dev/null +++ b/1-js/7-deeper/5-settimeout-setinterval/3-rewrite-settimeout-0/solution.md @@ -0,0 +1,23 @@ + + +```js run +let i = 0; + +let start = performance.now(); + +let timer = setInterval(count, 0); + +function count() { + + for(let j = 0; j < 1000000; j++) { + i++; + } + + if (i == 1000000000) { + alert("Done in " + (performance.now() - start) + 'ms'); + cancelInterval(timer); + } + +} +``` + diff --git a/1-js/7-deeper/5-settimeout-setinterval/3-rewrite-settimeout-0/task.md b/1-js/7-deeper/5-settimeout-setinterval/3-rewrite-settimeout-0/task.md new file mode 100644 index 00000000..bc208888 --- /dev/null +++ b/1-js/7-deeper/5-settimeout-setinterval/3-rewrite-settimeout-0/task.md @@ -0,0 +1,30 @@ +importance: 4 + +--- + +# Rewrite setTimeout with setInterval + +Rewrite the split counting function from setTimeout to setInterval: + +```js run +let i = 0; + +let start = performance.now(); + +function count() { + + if (i == 1000000000) { + alert("Done in " + (performance.now() - start) + 'ms'); + } else { + setTimeout(count, 0); + } + + for(let j = 0; j < 1000000; j++) { + i++; + } + +} + +count(); +``` + diff --git a/1-js/7-deeper/5-settimeout-setinterval/4-settimeout-result/solution.md b/1-js/7-deeper/5-settimeout-setinterval/4-settimeout-result/solution.md index 06ca186a..e652a3b3 100644 --- a/1-js/7-deeper/5-settimeout-setinterval/4-settimeout-result/solution.md +++ b/1-js/7-deeper/5-settimeout-setinterval/4-settimeout-result/solution.md @@ -1,6 +1,15 @@ -Ответы: -- `alert` выведет `100000000`. -- **3**, срабатывание будет после окончания работы `hardWork`. +Any `setTimeout` will run only after the current code has finished. -Так будет потому, что вызов планируется на `100 мс` от времени вызова `setTimeout`, но функция выполняется больше, чем `100 мс`, поэтому к моменту ее окончания время уже подошло и отложенный вызов выполняется тут же. \ No newline at end of file +The `i` will be the last one: `100000000`. + +```js run +let i = 0; + +setTimeout(() => alert(i), 100); // 100000000 + +// assume that the time to execute this function is >100ms +for(let j = 0; j < 100000000; j++) { + i++; +} +``` diff --git a/1-js/7-deeper/5-settimeout-setinterval/4-settimeout-result/task.md b/1-js/7-deeper/5-settimeout-setinterval/4-settimeout-result/task.md index 07c55194..faca4600 100644 --- a/1-js/7-deeper/5-settimeout-setinterval/4-settimeout-result/task.md +++ b/1-js/7-deeper/5-settimeout-setinterval/4-settimeout-result/task.md @@ -2,31 +2,26 @@ importance: 5 --- -# Что выведет setTimeout? +# What will setTimeout show? -В коде ниже запланирован запуск `setTimeout`, а затем запущена тяжёлая функция `hardWork`, выполнение которой занимает более долгое время, чем интервал до срабатывания таймера. +In the code below there's a `setTimeout` call scheduled, then a heavy calculation is run, that takes more than 100ms to finish. -Когда сработает `setTimeout`? Выберите нужный вариант: +When the scheduled function will run? -1. До выполнения `hardWork`. -2. Во время выполнения `hardWork`. -3. Сразу же по окончании `hardWork`. -4. Через 100 мс после окончания `hardWork`. +1. After the loop. +2. Before the loop. +3. In the beginning of the loop. -Что выведет `alert` в коде ниже? + +What `alert` is going to show? ```js -setTimeout(function() { - alert( i ); -}, 100); +let i = 0; -var i; +setTimeout(() => alert(i), 100); // ? -function hardWork() { - // время выполнения этого кода >100 мс, сам код неважен - for (i = 0; i < 1e8; i++) hardWork[i % 2] = i; +// assume that the time to execute this function is >100ms +for(let j = 0; j < 100000000; j++) { + i++; } - -hardWork(); ``` - diff --git a/1-js/7-deeper/5-settimeout-setinterval/article.md b/1-js/7-deeper/5-settimeout-setinterval/article.md index f1495dda..d639665c 100644 --- a/1-js/7-deeper/5-settimeout-setinterval/article.md +++ b/1-js/7-deeper/5-settimeout-setinterval/article.md @@ -1,4 +1,4 @@ -# Scheduling: setTimeout and setInterval [todo] +# Scheduling: setTimeout and setInterval [todo tasks] There are two methods to schedule function execution: @@ -207,98 +207,128 @@ And this is the picture for recursive `setTimeout`: 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. +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 garbage collected, even if there are no other references to it. ```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. +For `setInterval` the function stays in memory until `cancelInterval` is called. -Так как функция также тянет за собой всё замыкание, то ставшие неактуальными, но не отменённые `setInterval` могут приводить к излишним тратам памяти. +A function references outer lexical environment, so, while it lives, certain outer variables live too. They may take much more memory than the function itself. That's a good reason to keep an eye on scheduled functions` and cancel them once they are not needed. ```` -## Минимальная задержка таймера +## Zero timeout -У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4 мс в современных браузерах. В более старых она может быть больше и достигать 15 мс. +There's a special use case: `setTimeout(func, 0)`. -По [стандарту](http://www.w3.org/TR/html5/webappapis.html#timers), минимальная задержка составляет 4 мс. Так что нет разницы между `setTimeout(..,1)` и `setTimeout(..,4)`. +This plans the execution of `func` as soon as possible. But scheduler will invoke it only after the current code is complete. -Посмотреть минимальное разрешение "вживую" можно на следующем примере. +So the function is planned to run "right after" the current code. In other words, *asynchronously*. -**В примере ниже каждая полоска удлиняется вызовом `setInterval` с указанной на ней задержкой -- от 0 мс (сверху) до 20 мс (внизу).** +For instance, this outputs "Hello", then immediately "World": -Позапускайте его в различных браузерах. Вы заметите, что несколько первых полосок анимируются с одинаковой скоростью. Это как раз потому, что слишком маленькие задержки таймер не различает. +```js run +setTimeout(() => alert("World"), 0); -[iframe border="1" src="setinterval-anim" link edit] - -```warn -В Internet Explorer, нулевая задержка `setInterval(.., 0)` не сработает. Это касается именно `setInterval`, т.е. `setTimeout(.., 0)` работает нормально. +alert("Hello"); ``` -```smart header="Откуда взялись эти 4 мс?" -Почему минимальная задержка -- 4 мс, а не 1 мс? Зачем она вообще существует? +The trick is also used to split a CPU-hungry task. -Это -- "привет" от прошлого. Браузер Chrome как-то пытался убрать минимальную задержку в своих ранних версиях, но оказалось, что существуют сайты, которые используют `setTimeout(..,0)` рекурсивно, создавая тем самым "асинхронный цикл". И, если задержку совсем убрать, то будет 100% загрузка процессора, такой сайт "подвесит" браузер. +For instance, syntax highlighting script, used to colorize code examples on this page, is quite CPU-heavy. To hightlight the code, it analyzes it, creates many colored highlighting elements, adds them to the document -- for a big text that takes a lot. It may even cause the browser to "hang", that's unacceptable. -Поэтому, чтобы не ломать существующие скрипты, решили сделать задержку. По возможности, небольшую. На время создания стандарта оптимальным числом показалось 4 мс. -``` +So we can split the long text to pieces. First 100 lines, then plan another 100 lines using `setTimeout(...,0)`, and so on. -## Реальная частота срабатывания +As a simpler example -- here's a counting function from `1` to `1000000000`. -В ряде ситуаций таймер будет срабатывать реже, чем обычно. Задержка между вызовами `setInterval(..., 4)` может быть не 4 мс, а 30 мс или даже 1000 мс. +If you run it, the CPU will hang. For server-side JS that's clearly noticeable, and if you are running it in-browser, then try to scroll and click other buttons on the page -- you'll see that whole Javascript actually is paused, no other actions work until it finishes. -- Большинство браузеров (десктопных в первую очередь) продолжают выполнять `setTimeout/setInterval`, даже если вкладка неактивна. +```js run +let i = 0; - При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в "фоновой" вкладке будет срабатывать таймер, но редко. -- При работе от батареи, в ноутбуке -- браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек. -- При слишком большой загрузке процессора JavaScript может не успевать обрабатывать таймеры вовремя. При этом некоторые запуски `setInterval` будут пропущены. +let start = performance.now(); -**Вывод: на частоту 4 мс стоит ориентироваться, но не стоит рассчитывать.** +function count() { -```online -Посмотрим снижение частоты в действии на небольшом примере. + for(let j = 0; j < 1000000000; j++) { + i++; + } -При клике на кнопку ниже запускается `setInterval(..., 90)`, который выводит список интервалов времени между 25 последними срабатываниями таймера. Запустите его. Перейдите на другую вкладку и вернитесь. - -
- - - - - -Если ваш браузер увеличивает таймаут при фоновом выполнении вкладки, то вы увидите увеличенные интервалы, помеченные красным. - -Кроме того, вы заметите, что таймер не является идеально точным ;) +count(); ``` -## Разбивка долгих скриптов +Now the split version: -Нулевой или небольшой таймаут также используют, чтобы разорвать поток выполнения "тяжелых" скриптов. +```js run +let i = 0; -Например, скрипт для подсветки синтаксиса должен проанализировать код, создать много цветных элементов для подсветки и добавить их в документ -- на большом файле это займёт много времени, браузер может даже подвиснуть, что неприемлемо. +let start = performance.now(); -Для того, чтобы этого избежать, сложная задача разбивается на части, выполнение каждой части запускается через мини-интервал после предыдущей, чтобы дать браузеру время. +function count() { -Например, осуществляется анализ и подсветка первых 100 строк, затем через 20 мс -- следующие 100 строк и так далее. При этом можно подстраиваться под CPU посетителя: замерять время на анализ 100 строк и, если процессор хороший, то в следующий раз обработать 200 строк, а если плохой -- то 50. В итоге подсветка будет работать с адекватной быстротой и без тормозов на любых текстах и компьютерах. + if (i == 1000000000) { + alert("Done in " + (performance.now() - start) + 'ms'); + } else { + setTimeout(count, 0); + } + + for(let j = 0; j < 1000000; j++) { + i++; + } + +} + +count(); +``` + + +Now the browser UI is fully functional. Pauses between `count` executions provide just enough "breath" for the browser to do something else, to react on other user actions. + +The notable thing is that both variants are comparable in speed. There's no much difference in the overall counting time. + +Note that `setTimeout(count, 0)` is planned before the counting. That's actually a small workaround. The [HTML5 standard](https://www.w3.org/TR/html5/webappapis.html#timers) says: "after five nested timers..., the interval is forced to be at least four milliseconds.". That limitation exists mainly for historical reasons. + + +For us it means that first 5 invocations will run one right after another, and then 4ms delay will be added between them. + +Here's the code with `setTimeout` at the end to compare: + +```js run +let i = 0; + +let start = performance.now(); + +function count() { + + for(let j = 0; j < 1000000; j++) { + i++; + } + + // moved to "after the job" +*!* + if (i == 1000000000) { + alert("Done in " + (performance.now() - start) + 'ms'); + } else { + setTimeout(count, 0); + } +*/!* + +} + +count(); +``` + +If you run it, easy to notice that it takes significantly more time. Just because of these additional delays. The good thing is that the browser now has even more time between the invocations to go elsewhere. + + +```smart header="Server-side Javascript" +For server-side Javascript, the "four ms" limitation does not exist. Also, there are other ways to schedule an immediate asynchronous job. In Node.JS that's [process.nextTick](https://nodejs.org/api/process.html) and [setImmediate](https://nodejs.org/api/timers.html). +``` ## Итого diff --git a/1-js/7-deeper/5-settimeout-setinterval/setinterval-anim.view/index.html b/1-js/7-deeper/5-settimeout-setinterval/setinterval-anim.view/index.html deleted file mode 100644 index caa33217..00000000 --- a/1-js/7-deeper/5-settimeout-setinterval/setinterval-anim.view/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/7-deeper/5-settimeout-setinterval/setinterval-interval.png b/1-js/7-deeper/5-settimeout-setinterval/setinterval-interval.png index 613cbf77..060b2c29 100644 Binary files a/1-js/7-deeper/5-settimeout-setinterval/setinterval-interval.png and b/1-js/7-deeper/5-settimeout-setinterval/setinterval-interval.png differ diff --git a/1-js/7-deeper/5-settimeout-setinterval/setinterval-interval@2x.png b/1-js/7-deeper/5-settimeout-setinterval/setinterval-interval@2x.png index 3344a1ae..4071849c 100644 Binary files a/1-js/7-deeper/5-settimeout-setinterval/setinterval-interval@2x.png and b/1-js/7-deeper/5-settimeout-setinterval/setinterval-interval@2x.png differ diff --git a/1-js/7-deeper/5-settimeout-setinterval/settimeout-interval.png b/1-js/7-deeper/5-settimeout-setinterval/settimeout-interval.png index a95ba01c..6c473a33 100644 Binary files a/1-js/7-deeper/5-settimeout-setinterval/settimeout-interval.png and b/1-js/7-deeper/5-settimeout-setinterval/settimeout-interval.png differ diff --git a/1-js/7-deeper/5-settimeout-setinterval/settimeout-interval@2x.png b/1-js/7-deeper/5-settimeout-setinterval/settimeout-interval@2x.png index 8a98bd1a..dd45e324 100644 Binary files a/1-js/7-deeper/5-settimeout-setinterval/settimeout-interval@2x.png and b/1-js/7-deeper/5-settimeout-setinterval/settimeout-interval@2x.png differ diff --git a/1-js/plan2.txt b/1-js/plan2.txt index 4032bc8c..7b605624 100644 --- a/1-js/plan2.txt +++ b/1-js/plan2.txt @@ -72,12 +72,9 @@ recursion ( closures LE outer returning a function - -not function props - new Function? - counter object? - - -<<< memory management of closures? (AFTER CLOSURES! Сюрприз нельзя получить!!!) + garbage collection + var + window function-object @@ -87,7 +84,7 @@ function-object new function -scheduling: settimeout, setinterval +scheduling: settimeout, setinterval [todo tasks] recursive settimeout <<< bind, fixing context diff --git a/archive/nfe_more.md b/archive/nfe_more.md deleted file mode 100644 index b8e642cb..00000000 --- a/archive/nfe_more.md +++ /dev/null @@ -1,52 +0,0 @@ - -Let's output it using `alert`: - -```js -//+ run -function sayHi() { - alert( "Hello" ); -} - -*!* -alert( sayHi ); // shows the function code -*/!* -``` - -Note that there are no brackets after `sayHi` in the last line. The function is not called there. - -The code above only shows the string representation of the function, that is it's source code. - -[cut] - - -As the function is a value, we can copy it to another variable: - -```js -//+ run no-beautify -function sayHi() { // (1) - alert( "Hello" ); -} - -let func = sayHi; // (2) -func(); // Hello // (3) - -sayHi = null; // (4) -sayHi(); // error -``` - -
    -
  1. Function declaration `(1)` creates the function and puts it into the variable `sayHi`"
  2. -
  3. Line `(2)` copies it into variable `func`. - -Please note again: there are no brackets after `sayHi`. If they were, then the call `let func = sayHi()` would write a *result* of `sayHi()` into `func`, not the function itself.
  4. -
  5. At the moment `(3)` the function can be called both as `sayHi()` and `func()`.
  6. -
  7. ...We can overwrite `sayHi` easily. As well as `func`, they are normal variables. Naturally, the call attempt would fail in the case `(4)`.
  8. -
- -[smart header="A function is an \"action value\""] -Regular values like strings or numbers represent the *data*. - -A function can be perceived as an *action*. - -A function declaration creates that action and puts it into a variable of the given name. Then we can run it via brackets `()` or copy into another variable. -[/smart] diff --git a/figures.sketch b/figures.sketch index a0b49037..b9a36f5f 100644 Binary files a/figures.sketch and b/figures.sketch differ