Функция, отложенная через setTimeout(..0)
выполнится не ранее следующего «тика» таймера, минимальная частота которого может составлять от 4 до 1000 мс. И, конечно же, это произойдёт после того, как все текущие изменения будут перерисованы.
Но нужна ли нам эта дополнительная задержка? Как правило, используя setTimeout(func, 0)
, мы хотим перенести выполнение func
на «ближайшее время после текущего кода», и какая-то дополнительная задержка нам не нужна. Если бы была нужна – мы бы её указали вторым аргументом вместо 0
.
Метод setImmediate(func)
Для того, чтобы поставить функцию в очередь на выполнение без задержки, в Microsoft предложили метод setImmediate(func). Он реализован в IE10+ и на платформе Node.JS.
У setImmediate
единственный аргумент – это функция, выполнение которой нужно запланировать.
В других браузерах setImmediate
нет, но его можно эмулировать, используя, к примеру, метод postMessage, предназначенный для пересылки сообщений от одного окна другому. Детали работы с postMessage
вы найдёте в статье Общение окон с разных доменов: postMessage. Желательно читать её после освоения темы «События».
Полифил для setImmediate
через postMessage
:
if (!window.setImmediate) window.setImmediate = (function() {
var head = { }, tail = head; // очередь вызовов, 1-связный список
var ID = Math.random(); // уникальный идентификатор
function onmessage(e) {
if(e.data != ID) return; // не наше сообщение
head = head.next;
var func = head.func;
delete head.func;
func();
}
if(window.addEventListener) { // IE9+, другие браузеры
window.addEventListener('message', onmessage);
} else { // IE8
window.attachEvent( 'onmessage', onmessage );
}
return function(func) {
tail = tail.next = { func: func };
window.postMessage(ID, "*");
};
}());
Есть и более сложные эмуляции, включая MessageChannel для работы с Web Workers и хитрый метод для поддержки IE8-: https://github.com/NobleJS/setImmediate. Все они по существу являются «хаками», направленными на то, чтобы обеспечить поддержку setImmediate
в тех браузерах, где его нет.
Тест производительности
Чтобы сравнить реальную частоту срабатывания – измерим время на 100 последовательных вызовов при setTimeout(..0)
по сравнению с setImmediate
:
if (!window.setImmediate) window.setImmediate = (function() {
var head = {},
tail = head; // очередь вызовов, 1-связный список
var ID = Math.random(); // уникальный идентификатор
function onmessage(e) {
if (e.data != ID) return; // не наше сообщение
head = head.next;
var func = head.func;
delete head.func;
func();
}
if (window.addEventListener) { // IE9+, другие браузеры
window.addEventListener('message', onmessage);
} else { // IE8
window.attachEvent('onmessage', onmessage);
}
return function(func) {
tail = tail.next = {
func: func
};
window.postMessage(ID, "*");
};
}());
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script src="setImmediate.js"></script>
</head>
<body>
<button onclick="testTimeout()">testTimeout</button>
<button onclick="testImmediate()">testImmediate</button>
<script>
function testTimeout() {
var start = new Date();
var i = 0;
setTimeout(function go() {
i++;
if (i == 100) {
alert(new Date - start);
} else {
setTimeout(go, 0);
}
}, 0);
}
function testImmediate() {
var start = new Date();
var i = 0;
setImmediate(function go() {
i++;
if (i == 100) {
alert(new Date - start);
} else {
setImmediate(go);
}
});
}
</script>
</body>
</html>
Запустите пример выше – и вы увидите реальную разницу во времени между setTimeout(.., 0)
и setImmediate
. Да, она может быть более в 50, 100 и более раз.