Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык JavaScript часть1.pdf
Скачиваний:
190
Добавлен:
22.03.2016
Размер:
8.92 Mб
Скачать

var leader = {

name: "Василий Иванович" };

var soldier = { name: "Петька"

};

// эти объекты ссылаются друг на друга! leader.soldier = soldier; soldier.leader = leader;

var team = [leader, soldier];

1.Может ли это сделать прямой вызов JSON.stringify(team)? Если нет, то почему?

2.Какой подход вы бы предложили для чтения и восстановления таких объектов?

Крешению

setTimeout и setInterval

Почти все реализации JavaScript имеют внутренний таймер-планировщик, который позволяет задавать вызов функции через заданный период времени.

В частности, эта возможность поддерживается в браузерах и в сервере Node.JS.

setTimeout

Синтаксис:

var timerId = setTimeout(func / code, delay[, arg1, arg2...])

Параметры:

func/code

Функция или строка кода для исполнения. Строка поддерживается для совместимости, использовать её не рекомендуется.

delay

Задержка в милисекундах, 1000 милисекунд равны 1 секунде.

arg1, arg2

Аргументы, которые нужно передать функции. Не поддерживаются в IE9-.

Исполнение функции произойдёт спустя время, указанное в параметре delay.

Например, следующий код вызовет func()через одну секунду:

function func() { alert( 'Привет' );

}

setTimeout(func, 1000);

С передачей аргументов (не сработает в IE9-):

function func(phrase, who) { alert( phrase + ', ' + who );

}

setTimeout(func, 1000, "Привет", "Вася"); // Привет, Вася

Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки.

То есть такая запись тоже сработает:

setTimeout("alert('Привет')", 1000);

Однако, использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости.

Вместо них используйте анонимные функции, вот так:

setTimeout(function() { alert('Привет') }, 1000);

Отмена исполнения clearTimeout

Функция setTimeoutвозвращает числовой идентификатор таймера timerId, который можно использовать для отмены действия.

Синтаксис:

var timerId = setTimeout(...); clearTimeout(timerId);

В следующем примере мы ставим таймаут, а затем удаляем (передумали). В результате ничего не происходит.

var timerId = setTimeout(function() { alert(1) }, 1000); alert(timerId); // число идентификатор таймера

clearTimeout(timerId);

alert(timerId); // всё ещё число, оно не обнуляется после отмены

Как видно из alert, в браузере идентификатор таймера является обычным числом. Другие

JavaScript-окружения, например Node.JS, могут возвращать объект таймера, с дополнительными

методами.

Такие разночтения вполне соответствуют стандарту просто потому, что в спецификации JavaScript про таймеры нет ни слова.

Таймеры — это надстройка над JavaScript, которая описана в секции Timers стандарта HTML5 для браузеров и в документации к Node.JS — для сервера.

setInterval

Метод setIntervalимеет синтаксис, аналогичный setTimeout.

var timerId = setInterval(func / code, delay[, arg1, arg2...])

Смысл аргументов — тот же самый. Но, в отличие от setTimeout, он запускает выполнение

функции не один раз, а регулярно повторяет её через указанный интервал времени. Остановить исполнение можно вызовом clearInterval(timerId).

Следующий пример при запуске станет выводить сообщение каждые две секунды, пока не пройдёт 5 секунд:

//начать повторы с интервалом 2 сек var timerId = setInterval(function() {

alert( "тик" ); }, 2000);

//через 5 сек остановить повторы setTimeout(function() {

clearInterval(timerId); alert( 'стоп' );

}, 5000);

Модальные окна замораживают время в Chrome/Opera/Safari

Что будет, если долго не жать OKна появившемся alert? Это зависит от браузера.

В браузерах Chrome, Opera и Safari внутренний таймер «заморожен» во время показа alert/confirm/prompt. А вот в IE и Firefox внутренний таймер продолжит идти.

Поэтому, если закрыть alertпосле небольшой паузы, то в Firefox/IE следующий alertбудет

показан сразу же (время подошло), а в Chrome/Opera/Safari — только через 2 секунды после закрытия.

Рекурсивный setTimeout

Важная альтернатива setInterval— рекурсивный setTimeout:

/** вместо:

var timerId = setInterval(function() { alert( "тик" );

}, 2000); */

var timerId = setTimeout(function tick() { alert( "тик" );

timerId = setTimeout(tick, 2000); }, 2000);

В коде выше следующее выполнение планируется сразу после окончания предыдущего.

Рекурсивный setTimeout— более гибкий метод тайминга, чем setInterval, так как время

до следующего выполнения можно запланировать по-разному, в зависимости от результатов текущего.

Например, у нас есть сервис, который в 5 секунд опрашивает сервер на предмет новых данных. В случае, если сервер перегружен, можно увеличивать интервал опроса до 10, 20, 60 секунд… А потом вернуть обратно, когда всё нормализуется.

Если у нас регулярно проходят грузящие процессор задачи, то мы можем оценивать время, потраченное на их выполнение, и планировать следующий запуск раньше или позже.

Рекурсивный setTimeoutгарантирует паузу между вызовами, setInterval— нет.

Давайте сравним два кода. Первый использует setInterval:

var i = 1; setInterval(function() {

func(i); }, 100);

Второй использует рекурсивный setTimeout:

var i = 1; setTimeout(function run() {

func(i); setTimeout(run, 100);

}, 100);

При setIntervalвнутренний таймер будет срабатывать чётко каждые 100мс и вызывать func(i):

Вы обратили внимание?…

Реальная пауза между вызовами funcпри setIntervalменьше, чем указана в коде!

Это естественно, ведь время работы функции никак не учитывается, оно «съедает» часть интервала.

Возможно и такое что funcоказалась сложнее, чем мы рассчитывали и выполнялась дольше, чем

100мс.

В этом случае интерпретатор будет ждать, пока функция завершится, затем проверит таймер и, если время вызова setIntervalуже подошло (или прошло), то следующий вызов произойдёт

сразу же.

Если функция и выполняется дольше, чем пауза setInterval, то вызовы будут происходить вообще без перерыва.

Исключением является IE, в котором таймер «застывает» во время выполнения JavaScript.

А так будет выглядить картинка с рекурсивным setTimeout:

При рекурсивном setTimeoutзадержка всегда фиксирована и равна 100мс.

Это происходит потому, что каждый новый запуск планируется только после окончания текущего.

Управление памятью

Сборщик мусора в JavaScript не чистит функции, назначенные в таймерах, пока таймеры актуальны.

При передаче функции в setInterval/setTimeoutсоздаётся внутренняя ссылка на неё, через

которую браузер её будет запускать, и которая препятствует удалению из памяти, даже если функция анонимна.

// Функция будет жить в памяти, пока не сработал (или не был очищен) таймер setTimeout(function() {}, 100);

Для setTimeout— внутренняя ссылка исчезнет после исполнения функции.

Для setInterval— ссылка исчезнет при очистке таймера.

Так как функция также тянет за собой всё замыкание, то ставшие неактуальными, но не отменённые setIntervalмогут приводить к излишним тратам памяти.

Минимальная задержка таймера

У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс.

По стандарту , минимальная задержка составляет 4мс. Так что нет разницы между setTimeout(..,1)и setTimeout(..,4).

Посмотреть минимальное разрешение «вживую» можно на следующем примере.

В примере ниже каждая полоска удлиняется вызовом setIntervalс указанной на ней задержкой — от 0мс (сверху) до 20мс (внизу).

Позапускайте его в различных браузерах. Вы заметите, что несколько первых полосок анимируются с одинаковой скоростью. Это как раз потому, что слишком маленькие задержки таймер не различает.

Старт Стоп

0

2

4

6

8

10

12

14

16

18

20

Важно:

В Internet Explorer, нулевая задержка setInterval(.., 0)не сработает. Это касается именно setInterval, т.е. setTimeout(.., 0)работает нормально.

Откуда взялись эти 4мс?

Почему минимальная задержка — 4мс, а не 1мс? Зачем она вообще существует?

Это — «привет» от прошлого. Браузер Chrome как-то пытался убрать минимальную задержку в своих ранних версиях, но оказалось, что существуют сайты, которые используют setTimeout(..,0)рекурсивно, создавая тем самым «асинхронный цикл». И, если задержку

совсем убрать, то будет 100% загрузка процессора, такой сайт «подвесит» браузер.

Поэтому, чтобы не ломать существующие скрипты, решили сделать задержку. По возможности, небольшую. На время создания стандарта оптимальным числом показались

4мс.

Реальная частота срабатывания

В ряде ситуаций таймер будет срабатывать реже, чем обычно. Задержка между вызовами setInterval(..., 4)может быть не 4мс, а 30мс или даже 1000мс.

Большинство браузеров (десктопных в первую очередь) продолжают выполнять setTimeout/setInterval, даже если вкладка неактивна.

При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в «фоновой» вкладке будет срабатывать таймер, но редко.

При работе от батареи, в ноутбуке — браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек.

При слишком большой загрузке процессора JavaScript может не успевать обрабатывать

таймеры вовремя. При этом некоторые запуски setIntervalбудут пропущены.

Вывод: на частоту 4мс стоит ориентироваться, но не стоит рассчитывать.

Разбивка долгих скриптов

Нулевой или небольшой таймаут также используют, чтобы разорвать поток выполнения «тяжелых» скриптов.

Например, скрипт для подсветки синтаксиса должен проанализировать код, создать много цветных элементов для подсветки и добавить их в документ — на большом файле это займёт много времени, браузер может даже подвиснуть, что неприемлемо.

Для того, чтобы этого избежать, сложная задача разбивается на части, выполнение каждой части запускается через мини-интервал после предыдущей, чтобы дать браузеру время.

Например, осуществляется анализ и подсветка первых 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).

Задачи

Вывод чисел каждые 100мс

важность: 5

Напишите функцию printNumbersInterval(), которая последовательно выводит в консоль

числа от 1 до 20, с интервалом между числами 100мс. То есть, весь вывод должен занимать

2000мс, в течение которых каждые 100мс в консоли появляется очередное число.

Нажмите на кнопку, открыв консоль, для демонстрации:

printNumbersInterval()

P.S. Функция должна использовать setInterval.

К решению

Вывод чисел каждые 100мс, через setTimeout

важность: 5

Сделайте то же самое, что в задаче Вывод чисел каждые 100мс, но с использованием рекурсивного setTimeoutвместо setInterval.

К решению

Для подсветки setInterval или setTimeout?

важность: 5

Стоит задача: реализовать подсветку синтаксиса в длинном коде при помощи JavaScript, для онлайн-редактора кода. Это требует сложных вычислений, особенно загружает процессор генерация дополнительных элементов страницы, визуально осуществляющих подсветку.

Поэтому решаем обрабатывать не весь код сразу, что привело бы к зависанию скрипта, а разбить работу на части: подсвечивать по 20 строк раз в 10мс.

Как мы знаем, есть два варианта реализации такой подсветки:

1. Через setInterval, с остановкой по окончании работы:

timer = setInterval(function() {

if (есть еще что подсветить) highlight(); else clearInterval(timer);

}, 10);

2. Через рекурсивный setTimeout:

setTimeout(function go() { highlight();

if (есть еще что подсветить) setTimeout(go, 10); }, 10);

Какой из них стоит использовать? Почему?

К решению

Что выведет setTimeout?

важность: 5

В коде ниже запланирован запуск setTimeout, а затем запущена тяжёлая функция hardWork, выполнение которой занимает более долгое время, чем интервал до срабатывания таймера.

Когда сработает setTimeout? Выберите нужный вариант:

1.До выполнения hardWork.

2.Во время выполнения hardWork.

3.Сразу же по окончании hardWork.

4.Через 100мс после окончания hardWork.

Что выведет alertв коде ниже?

setTimeout(function() { alert( i );

}, 100);

var i;

function hardWork() {

// время выполнения этого кода >100мс, сам код неважен for (i = 0; i < 1e8; i++) hardWork[i % 2] = i;

}

hardWork();

К решению

Что выведет после setInterval?

важность: 5

В коде ниже запускается setIntervalкаждые 10мс, и через 50мс запланирована его отмена.

После этого запущена тяжёлая функция f, выполнение которой (мы точно знаем) потребует более 100мс.

Сработает ли setInterval, как и когда?

Варианты:

1.Да, несколько раз, в процессе выполнения f.

2.Да, несколько раз, сразу после выполнения f.

3.Да, один раз, сразу после выполнения f.

4.Нет, не сработает.

5.Может быть по-разному, как повезёт.

Что выведет alertв строке (*)?

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();

К решению

Кто быстрее?

важность: 5

Есть два бегуна:

var runner1 = new Runner(); var runner2 = new Runner();

У каждого есть метод step(), который делает шаг, увеличивая свойство steps.

Конкретный код метода step()не имеет значения, важно лишь что шаг делается не мгновенно, он требует небольшого времени.

Если запустить первого бегуна через setInterval, а второго — через вложенный setTimeout— какой сделает больше шагов за 5 секунд?

// первый? setInterval(function() {

runner1.step(); }, 15);

// или второй? setTimeout(function go() {

runner2.step(); setTimeout(go, 15);

}, 15);

setTimeout(function() { alert( runner1.steps ); alert( runner2.steps );

}, 5000);

К решению

Функция-задержка

важность: 5

Напишите функцию delay(f, ms), которая возвращает обёртку вокруг f, задерживающую вызов на msмиллисекунд.

Например:

function f(x) { alert( x );

}

var f1000 = delay(f, 1000); var f1500 = delay(f, 1500);

f1000("тест"); // выведет "тест" через 1000 миллисекунд f1500("тест2"); // выведет "тест2" через 1500 миллисекунд

Упрощённо можно сказать, что delayвозвращает «задержанный на ms» вариант f.

В примере выше у функции только один аргумент, но delayдолжна быть универсальной: передавать любое количество аргументов и контекст this.

Открыть песочницу с тестами для задачи.

К решению

Вызов не чаще чем в N миллисекунд

важность: 5

Напишите функцию debounce(f, ms), которая возвращает обёртку, которая передаёт вызов f

не чаще, чем раз в msмиллисекунд.

«Лишние» вызовы игнорируются. Все аргументы и контекст — передаются.

Например:

function f() { ... }

var f = debounce(f, 1000);

f(1); // выполнится сразу же f(2); // игнор

setTimeout( function() { f(3) }, 100); // игнор (прошло только 100мс) setTimeout( function() { f(4) }, 1100); // выполнится

setTimeout( function() { f(5) }, 1500); // игнор

Упрощённо можно сказать, что debounceвозвращает вариант f, срабатывающий не чаще чем раз в msмиллисекунд.

Открыть песочницу с тестами для задачи.

К решению

Тормозилка

важность: 5

Напишите функцию throttle(f, ms)— «тормозилку», которая возвращает обёртку, передающую вызов fне чаще, чем раз в msмиллисекунд.

У этой функции должно быть важное существенное отличие от debounce: если

игнорируемый вызов оказался последним, т.е. после него до окончания задержки ничего нет

— то он выполнится.

Чтобы лучше понять, откуда взялось это требование, и как throttleдолжна работать — разберём реальное применение, на которое и ориентирована эта задача.

Например, нужно обрабатывать передвижения мыши.

В JavaScript это делается функцией, которая будет запускаться при каждом микропередвижении мыши и получать координаты курсора. По мере того, как мышь двигается, эта функция может запускаться очень часто, может быть 100 раз в секунду (каждые 10мс).

Функция обработки передвижения должна обновлять некую информацию на странице.

При этом обновление — слишком «тяжёлый» процесс, чтобы делать его при каждом микропередвижении. Имеет смысл делать его раз в 100мс, не чаще.

Пусть функция, которая осуществляет это обновление по передвижению, называется onmousemove.