Как стать автором
Обновить

Комментарии 55

Спасибо, пишите еще! Читаю все ваши статьи! :)
Спасибо за статью.
Но у вас категорично: либо экономия процессорного времени, либо стабильность. Можно совместить варианты.

Запускаем два(!) таймера одновременно. Один setTimeout, другой setInterval. У setInterval ставим время срабатывания чуть позже, чем у setTimeout. И там, и там, в самом начале, делаем clear для альтернативы и запускаем её снова.

На первый взгляд, решение достаточно грубое, но результатом мы получим следующее: когда не успевает срабатывать Timeout, реагирует Interval, и мы получаем необходимое нам обновление, в иных случаях, Interval, со своим высоким приоритетом, отменяется, и Timeout нормально срабатывает.

На практике, в зависимости от сложности функции, отрабатывающей по таймеру, и идущих одновременно вычислений, получаем от 10 до 40 процентов прироста производительности :)

Теперь можете расстреливать за некрасивую реализацию :)
ой, у меня так вопросов много возникло :) Что если на некоторых машинах setTimeout успеет отработать до setInterval? или вы предполагаете каждую итерацию как-то помечать, что двойное выполнение не совершать? И можно пояснить, как именно проблему мы решаем таким образом? setTimeout будет же еще и "плыть" с течением времени...
>>на некоторых машинах setTimeout успеет отработать до setInterval?

Эмм... Дак, собственно, в этом и смысл :)

Вот, схематично, без переменных:
setTimeout(function(){
clearInterval();
setInterval(arguments.callee, 12);
setTimeout(arguments.callee, 10);
/* Какой-то большой участок кода... */
}, 10);

setInterval(function(){
clearTimeout();
setTimeout(arguments.callee, 10);
/* Какой-то большой участок кода... */
}, 12);

Плавания не происходит :) Если setTimeout не успевает отработать в нужный момент, обязательно вызывается setInterval. В иные моменты, когда ресурсов достаточно, setTimeout отрабатывает спокойно.
лично я не понял, какой выигрыш в данной ситуации. Если setTimeout не сможет отработать в положенное врямя — он будет положен в очередь и выполнится в первый возможный момент. setInterval ситуацию никак не меняет, имхо
Попробую объяснить.
Хочется:
Таймерное событие, по возможности, стандартно встаёт в очередь, а не вклинивается в неё.
Необходимо:
Таймерное событие, в случае _действительно_ длинной очереди, всё равно обязано выполнится раньше, потеснив другое событие.

Именно для этого два таймера - сетИнтервал и сетТаймаут. Только разница задержки между ними в реальном коде, разумеется, должна быть больше. Ну и флаги надо вводить, исключающие двойное выполнение, это тоже вроде понятно.
там в очереди же выполнится то, которое раньше в него встало? Т.е. в нашем случае это setTimeout.

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

Об этом еще Lecompte писал
http://webo.in/articles/habrahabr/13-cpu…
>Таймерное событие, в случае _действительно_ длинной очереди, всё равно обязано выполнится раньше, потеснив другое событие.

"Потеснив другое событие" – интересная теория. :) А что на практике?
>>И можно пояснить, как именно проблему мы решаем таким образом?
Прошу прощения, пропустил эту фразу. На самом деле, проблему мы, в общем-то, не решаем. Просто чуть-чуть увеличиваем производительность (интерфейса, документа в целом и т.д.) в моменты "плавающей" тяжёлой загруженности.
>Просто чуть-чуть увеличиваем производительность…

"Увеличиваем производительность" за счет установки дополнительных таймеров, а также возможного сброса и повторной установки интервала?
Пасибище за статью.
1. Это применимо ко всем браузерам?
2. Один поток на JavaScript — он на все созданные экземпляры компонента, отображающего страницу, или все же как-то иначе (на каждое окно, закладку)?
1. да
2. один поток, значит только то что в один момент времени выполняется только один блок кода. То есть если работает какой-то блок, то ни события, ни таймер, ни отрисовка страницы не прерывают его выполнения. У некоторых браузеров (к примеру Опера) движок однако "ставит на паузу" выполнение кода, чтобы отрисовать страницу (или ее часть), однако затем продолжает с того места где остановился. Только потом выполняются обработчики таймера, и события.
Что такое "один поток" я в курсе ;-)
Меня интересует вопрос именно в моей формулировке — поток, в котором выполняется скрипт, он один на все созданные экземпляры компонента, отображающего страницу (грубо говоря, на каждую открытую страницу) или иначе — один на компонент или один на окно или один на закладку?..
Один на документ/страницу/закладку
Т.е. фреймы тоже имеют свой поток?
Кхм... сдается мне что нет :) Затрудняюсь ответить, но с точки зрения логики не должно быть. Видимо один поток JS выделяется на все связанные документы (в частности на зависимые фреймы и открытые из js окна). Если проще, то на все документы которые имеют пересекающиеся "области видимости" или доступ к одним и тем же документам. Если это не так, то может возникнуть конфликт и конкуренция среди потоков за чтение/запись одних и тех данных, чего быть не должно. Так что по всей видимости, фреймы не имеют своего потока.
т.е. на один глобальный объект window (но я сам точно не знаю, есть предположение, что от браузера к браузеру будет варьироваться)
Наверное, все же на один объект top - то есть корневой объект window.
хмм, выше window вроде navigator, если верить этой схеме
http://www.comptechdoc.org/independent/w…
а это не то

в общем, это, скорее, вопрос терминологии
top
Refers to the window object of the page at the top of the frames hierarchy.
Кхм... не совсем так. У каждого document есть свой window. Window в данном случае это объект отвечающий за отрисовку (вывод) самой страницы. document'ов может быть несколько, но сколько бы их не было столько же будет и window. Каждый frame имеет свой документ (пусть и пустой), соответственно и свой window (сюда так же относятся порожденные окна, по сути это тот же фрейм, только со своим способом обращения и в отдельном "окне" ОС). Все эти фреймы относятся к первому документу (window), который их и породил (содержал "код" вставки фрейма или открытия окна). Вложенность не имеет значения (и может быть сколь угодно большой, в разумных пределах конечно). У всех этих документов (window), включая первичный, есть ссылка на самый первый документ (то есть первичный) - top, но не на сам документ, а на его window.
ЗЫ window & document имеют ссылки друг на друга (у window это document, у document разное название от браузера к браузеру - по DOM это defaultView).
ЗЫ Вы как раз и привели цитату что значит top. По сути "страница без фреймов", это однофреймовая страница :)
>Один на документ/страницу/закладку

Проверьте свое предположение, например, в Firefox.
>1. да

Нет!!!
>1. да

Нет!

>движок однако "ставит на паузу" выполнение кода…

Как это можно проверить?
> Нет!
Что нет? Таймеры в разных браузерах работают по разному?
> Как это можно проверить?
Чисто визуально. Пока меняете элемент(ы) (в коде) можно на короткое время заметить промежуточный результат, который крайне редко когда нужен, а много чаще только ухудшает "картинку".
Другой вариант, попробуйте генерить JS-ом таблицу хотя бы 10 колонок, 100-200 строк, с довольно обильным вычислениями, количеством стилей и элементов внутри ячеек. В Опере вы увидите несколько промежуточных состояний (в 9.5 не знаю, не пробывал еще, а вот до этой версии однозначно), причем ячейки могут перекоситься так, что это будет напоминать набор прямоугольных блоков натыканных случайным образом, а вовсе не таблицу (причем во время и по окончании генерации).
>Таймеры в разных браузерах работают по разному?

Да, конечно. Чуть ниже в комментах я опубликовал небольшой кусочек кода. Прочтите этот коммент.

>попробуйте генерить JS-ом таблицу хотя бы 10 колонок...

Хотелось бы увидеть пример, а не самому его писать (я, как правило, так и делаю, если нужно что-то объяснить). Ну да ладно - на нет и суда нет...
Спасибо, материал пригодится.
P.S. К сожалению все чаще встречаю разработчиков не понимающих многих механизмов Javascript, так как им сразу подсовывают prototype или jquery. Скоро без этих библиотек никто знать не будет как к элементу обратиться :)
«Умная» очередь, 1-ый абзац:
Таймер будет ждать следующего удобного момент_а_ для того, что выполниться

Заметка хорошая. Жаль, что я не нашел там решения одной проблемы, как раз связанной с тем, что события таймеров откладываются — ожидал увидеть его (решение) тут.
Спасибо. Вариант решения я дал в комментариях — делать искусственное "окно" (например, в 1мс) для того, чтобы таймеры могли выполниться. Чуть выше дана и ссылка на пример реализации таких "окон".

Вообще же можно попробовать к посмотреть профиль AndrewSumin обратиться
http://jsx.ru/author.html
>Однако, стоит обратить внимание, Что при последующем срабатывании того же таймера (interval), он сбрасывается.

Что значит "он сбрасывается"?
в данном контексте не выставляется в очередь на выполнение, но продолжает проверяться
>в данном контексте не выставляется в очередь на выполнение

var id = window.setInterval(myFunc, 100);
Выставлено на выполнение каждые 100мс, так? Однако, выполнение может быть отложено. В каком же контексте понимать "не выставляется в очередь"?

>но продолжает проверяться

Что продолжает проверяться?
вроде в статье русским языком изложено то, что спрашивается на 5 абзацев (просто если в двух словах не понятно, то излагать долго). Я могу процитировать

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

Для начала, внутри первого блока JavaScript устанавливаются два таймера: на 10мс через setTimeout и на 10мс через setInterval. Из-за того, как и где этот таймер был выставлен (было выставлено 2 таймера, однако, тут и далее речь идет про один; почему так — объясняется чуть дальше), он в действительности срабатывает до того, как завершается первый блок кода. Заметим, однако, что таймер исполнится не сразу после окончания этого блока, а через некоторое время (это невозможно по причине наличия других потоков, состояние которых также нужно проверить). Вместо этого задержанная в выполнении функция посылается в очередь для выполнения в следующий свободный момент.

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

После того как наш первый блок JavaScript-кода закончит исполнение (наконец-то!), браузер тут же смотрит в очередь: что должно быть выполнено в следующий момент? В этом случае и клик мыши, и таймер ждут своей очереди на выполнение. Браузер берет один из них (обработчик клика мыши) и немедленно запускает его выполнение. Таймер будет ждать следующего удобного момент для того, чтобы выполниться.

Заметим, что обработчик клика мыши выполняется прежде, чем первый таймер. из-за этого выполнение таймера откладывается. Однако, стоит обратить внимание, Что при последующем срабатывании того же таймера (interval), он сбрасывается. Если вы думали, что все вызовы таймера, выставленного с определенным интервалом, создадут своеобразную очередь после выполнения большого блока кода безо всяких промежутков между собой, то вы ошибались. Вместо этого браузеры предпочитают просто ждать, пока не останется других, отложенных с помощью таймера, функций (в данном случае выставленных на выполнение через интервал), перед добавлением в их очередь выполнения.


Отвечаю на Ваши вопросы
1) Так
2) Стоит понимать "не выставляется в очередь" в том смысле, что у браузера есть очередь на выполнение JS-функций (поток), которая заполняется, в том числе, через setInterval. setInterval не может положить в эту очередь 2 раза одну и ту же функцию, только первый раз.
3) Браузер будет проверять setInterval каждые 100мс (в Вашем примере) и пытаться поставить в очередь на выполнение заявленную функцию (myFunc)
Вот небольшой пример, который нужно выполнить в Firefox'е. Поместите его в какой-нибудь документ и кликните раза три. Затем попытайтесь объяснить результат, появившийся в консоли:


(function () {
var i = 0, d = new Date;
document.onclick = function () {
var s = new Date;
while ((new Date) - s < 2000);
console.log((new Date) - d);
if (i == 0) {
var id = setInterval(function () {
if (i > 5) {
return clearInterval(id);
};
console.log(i++ + ': ' + ((new Date) - d));
}, 1000);
};
};
}());

Хороший пример. Думаю, его можно взять за образчик того, как работают timeout'ы в Firefox. У меня была первоначальная гипотеза, почему так:
5795
7800
9805
11810
13815
16735
21842
24050
0: 24141
1: 24524
2: 24564
3: 24583
4: 24742
5: 24819


После того, как в Safari я увидел примерно такую картину
5795
7800
0: 8000
9805
11810
13815
16735
21842
24050
1: 24524
2: 24564
3: 24583
4: 24742
5: 24819


мои догадки несколько подтвердились. С большой вероятностью (я не разработчик браузеров, могу только предполагать) каждая функция разбивается дополнительно на ряд "элементарных" (в частности, для onlick, это до условия и само выставление интервала)

У Вас каждый onclick выставляет после своего выполнения setInterval'ом анонимную функцию, которая после первого же выполнения завершается (что логично, ибо их может быть не больше 6). Но выставляется функция через разные setInterval ("сбрасываться" выполнение будет для одного и того же setInterval, но не для разных).

Т.е. в статье только один факт, который можно подвергнуть сомнению: что происходит, если одну и ту же функцию (желательно, не анонимную) назначить через setInterval и setTimeout? Должно ли "сброситься" ее выполнение и в каких браузерах?

И для Onlick / interval приоритет плавающий (т.е. у меня после полутора десятков кликов выполнились все 6 интервалов даже в Firefox.
>У Вас каждый onclick выставляет после своего выполнения setInterval'ом анонимную функцию

Ничего подобного! :) Будьте внимательней! :)

>Должно ли "сброситься" ее выполнение и в каких браузерах?

Не должно...

>у меня после полутора десятков кликов выполнились все 6 интервалов даже в Firefox

Естественно. Другое дело - как они выполняются в разных браузерах. А выполняются они по-разному: например, как Вы могли заметить, в Firefox'е они выполняются сразу. А вот, к примеру, в IE совершенно противоположная картина. И т.д...
т.е. Вы утверждаете, что код

document.onclick = function () {
...
setInterval(function(){...},1000);
...
};
мало того, что не создает анонимную функцию в качестве обработчика события, так еще и не создает анонимную функцию для выполнения с определенным интервалом? Так Ваши слова понимать?

Да там же замыкание на замыкание и замыканием погоняет!
>Так Ваши слова понимать?

Вы написали: "У Вас каждый onclick выставляет после своего выполнения setInterval'ом анонимную функцию...". А я Вам ответил, что это не так (обратите внимание на то, что я выделил). ;)
ну, в общем случае, не каждый. Но это слабо меняет картину :)
>ну, в общем случае, не каждый.

Да не "в общем случае", а так и есть. Иначе резульаты теста были вообще непонятны.

>Но это слабо меняет картину :)

Картина такая. Описываемое в статье - это одно из двух:
1. или относящееся лишь к поведению одной реализации (а их ведь больше, чем одна);
2. или вообще не соответствующее действительности.
> а так и есть.

если у нас кликов меньше 7, то Ваше заявление неверное.

> Описываемое в статье

Я не считаю показательным Ваш тестовый случай, чтобы делать из него какие-либо точные выводы. Он не демонстрирует заявленную в статье функциональность (не в том смысле, что расходится в фактах, а том смысле, что не воссоздает тестовое окружение :)
>если у нас кликов меньше 7, то Ваше заявление неверное.

Повторяю еще раз - метод setInterval вызывается лишь один раз (всегда и везде!!!). Это происходит в момент первого клика по документу. Во всех остальных кликах условие: if (i == 0) просто невыполняется.

>не демонстрирует заявленную в статье функциональность

Я уже написал то, что думаю об этой статье. Принимать мое мнение во внимание или нет - ваше личное дело.
> метод setInterval вызывается лишь один раз

ага, я понял, где у нас логика нарушена! Он вызывается не один раз. Итератор увеличивается только при срабатывании функции в setTimeout, а она срабатывает далеко после onclick!

onclick назначает функцию каждый раз, но все эти функции выполняются лишь до 6 раз друг за другом, 7 уже не выполняется, потому что глобальная i увеличилась. Если Вам интересно, чему равна i при каждом установлении setTimeout — можете проследить. Однако, лог консоли красноречиво об этом говорит.
Искренне извиняюсь, что заставил Вас разбираться с моей ошибкой. А ошибку допустил при копировании, пропустив икремент переменной i, который должен находиться в первой строке блока условия if (i == 0).
Примите извинения.
Сейчас, начав новую ветвь (т.к. здесь уже места нет для кода), опубликую тестовую функцию, которая наглядно покажет отличия в разных браузерах (IE, Opera, FF, Safari).
Вот функция, которая позволит проследить за различием в выполнении метода setInterval в браузерах IE, Opera, FF, Safari и проч.
При событии onload документа эта функция генерирует 3 (по умолчанию) клика на документе. Самому кликать не нужно, иначе в результатах будет сложнее разрираться. Первый из обработчиков клика запускает интервальную функцию.


window.onload = function () {
var numberOfCalls = 0;
/* numberOfCalls - numeration of function' calls (so no one won't be lost)
!!! it should be equaled always to zero
*/
var callsLimit = 5; // the limit of function calls
var interval = 1000; // desirable interval between calls (not reliable)
var execTime = 2000; // time of execution of every onclick handler
var numberOfClicks = 3; // number of simulated clicks on document
var startDate = new Date;
var reg = /function|object/;
var debugLog = function (aReg) {
var fnBody;
if (aReg.test(typeof (Debug)) &&
aReg.test(typeof (Debug.write))) {
fnBody = 'Debug.write(aMsg);'; // IE
} else if (aReg.test(typeof (opera)) &&
aReg.test(typeof (opera.postError))) {
fnBody = 'opera.postError(aMsg);'; // Opera
} else if (aReg.test(typeof (console)) &&
aReg.test(typeof (console.log))) {
fnBody = 'console.log(aMsg);'; // FF, Safari etc.
} else {
return new Function;
};
return new Function('aMsg', fnBody);
}(reg);
var onClickListener = function () {
var s = new Date;
while ((new Date) - s < execTime);
debugLog('onclick handler: '
.concat((new Date) - startDate,
'ms since document has loaded.'));
if (numberOfCalls == 0) {
numberOfCalls++;
debugLog('setting interval');
var id = setInterval(function () {
if (numberOfCalls > callsLimit) {
return clearInterval(id);
};
debugLog('interval call number '
.concat(numberOfCalls++,
': ', (new Date) - startDate,
'ms since document has loaded.',
' (expected: ',
interval * (numberOfCalls - 1),
'ms).'));
}, interval);
};
};
(function (aEvt, aDoc, aClicks, aReg) {
var func;
if (!aReg.test(typeof (setInterval))) {
return false;
};
if (aReg.test(typeof (aDoc.addEventListener)) &&
aReg.test(typeof (aDoc.createEvent)) &&
aReg.test(typeof (aDoc.dispatchEvent))) {
var evt = aDoc.createEvent('MouseEvents');
if (aReg.test(evt.initMouseEvent)) {
func = function () {
evt.initMouseEvent(aEvt, true, true, window,
0, 0, 0, 0, 0, false, false,
false, false, 0, null);
aDoc.dispatchEvent(evt);
};
aDoc.addEventListener(aEvt, onClickListener, false);
};
} else if (aReg.test(typeof (aDoc.attachEvent)) &&
aReg.test(typeof (aDoc.fireEvent))) {
func = function () {
aDoc.fireEvent(aEvt);
};
aDoc.attachEvent(aEvt = 'on' + aEvt, onClickListener);
};
if (func) {
debugLog('The script has started, '
.concat(aClicks,
' clicks on the document is expected.',
' (each of them should take ',
execTime, 'ms).'));
while (aClicks-- > 0) {
func();
};
};
}('click', document, numberOfClicks, reg));
};

ой, можно сразу какие-то выводы, а то на вторую дискуссию сил уже нет? :)
А к чему вторая дискуссия? Я выводы по поводу статьи уже сделал ранее. А эту функцию выложил, т.к. первый раз с ошибками опубликовал ее урезанный вариант.
да ни к чему, просто интересно, что показывает данная функция — Вы ведь потратили время, чтобы ее написать. Что один и тот же setInterval выставляет неограниченное количество функций в очередь выполнения?
>что показывает данная функция

Я ведь предлагал с самого начала посмотреть на результаты выполнения в Firefox. Смотрели? Заметили, что в отличие, например, от IE после выполнения трех обработчиков события click (на которые ушло времени столько, сколько нужно было на выполнение всех интервальных функций), все интервальные функции выполняются практически сразу?

>Вы ведь потратили время, чтобы ее написать.

Написано давно для форумных дискуссий. Т.ч. не печальтесь по поводу истраченного времени - у меня таких тестовых примеров еще много (и по этой теме еще есть, опять же подтверждающее то, что автор статьи не имеет представления о том, что написал). ;)
ммм, я уже высказал предположение, что приоритет на встраивание в очередь — плавающий, потому что после 15 кликов в Firefox 5 интервалов таки всплыли. В Safari это случилось еще и раньше. Т.е. у каждого браузера есть свои правила по созданию этой очереди. Я даже не предполагал, что на них (различия в правилах) стоит как-то закладываться при создании приложений.
>ммм, я уже высказал предположение...

Вы мне ответьте - в вашем Firefox'е после трех сгенерированных кликов все интервальные функции выполняются сразу?

>Я даже не предполагал, что на них (различия в правилах) стоит как-то закладываться при создании приложений

А как на это не обращать внимание, если в IE, например, метод setInterval работает, как часы (т.е. интервал между вызовами функций всегда тот, который был задан), а в Firefox'е функции могут быть вызваны целым "скопом" (т.е. без интервала), если до этого скриптовый движок был "сильно" занят?
значит, нужно использовать setTimeout, который гарантирует время между итерациями. Я к тому клоню, что знать отличия нужно, но закладываться на них — нет. Но это лично мое мнение.

у меня получилось примерно следующее:

The script has started, 3 clicks on the document is expected. (each of them should take 2000ms).
onclick handler: 2039ms since document has loaded.
setting interval
onclick handler: 4043ms since document has loaded.
onclick handler: 6045ms since document has loaded.
interval call number 1: 6274ms since document has loaded. (expected: 1000ms).
interval call number 2: 7277ms since document has loaded. (expected: 2000ms).
interval call number 3: 8282ms since document has loaded. (expected: 3000ms).
interval call number 4: 9279ms since document has loaded. (expected: 4000ms).
interval call number 5: 10283ms since document has loaded. (expected: 5000ms).
>у меня получилось примерно следующее

Это в Firefox'е? А какая версия?
3 RC2 вроде
Ну а я-то проверял в предыдущих версиях. А Вы какой дебаггер на FF3 установили?
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории