Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Отложенные вычисления в JavaScript (https://javascript.ru/forum/misc/11978-otlozhennye-vychisleniya-v-javascript.html)

Kolyaj 24.09.2010 10:43

Отложенные вычисления в JavaScript
 
Ещё одна серия статей, на этот раз про отложенные вычисления. Описал, правда, не всё, что хотел, пара паттернов у самого ещё не уложились в голове.

В процессе написания сделал для себя небольшое (а может даже большое) открытие.

Цитата:

IE устанавливает таймер на следующий запуск после выполнения callback-функции, а остальные браузеры — до. Поэтому, например, анимация, реализованная с помощью setInterval в Internet Explorer всегда будет медленнее, чем в других браузерах.

рони 24.09.2010 12:01

мысли в слух ...
Цитата:

function f() {
var a = 1;
setTimeout('alert(a)', 2000); // Через две секунды будет ошибка,
// т.к. a не определена в глобальной области видимости.
}
но можно веть так написать
function f() {
    var a = 1;
    setTimeout('alert('+a+')', 2000);
}
f();

и тогда ошибки не будет.

Kolyaj 24.09.2010 12:03

Можно, если только a -- значение примитивного типа.

subzey 24.09.2010 12:04

http://alljs.ru/articles/timeout/overview.html
Цитата:

Сообщение от Kolyaj
setTimeout(function() {…}, 1); // Ставим минимально возможный интервал

Минимально возможный интервал — 0. Код ставится в очередь, и выполнится по окончанию блока <script> (или каскада событий и обработчиков событий, в общем, «потока»).

Вот тестовый пример, который работает идентично во всех браузерах, включая престарелый IE6
<a href="javascript:writeLog('click default action')" id="test">Клицк ми!</a>
<pre id="log"></pre>
<script>
	setTimeout(function(){
		writeLog("Log started"); /* а функция-то еще не определена! */
	}, 0)
	writeLog = function(str){
		document.getElementById('log').innerHTML += str + "<br />";
	};
	test = document.getElementById("test");
	test.onclick = function(){
		writeLog('click event handler');
		setTimeout(function(){
			writeLog('timeout at click');
		}, 0)
	}
	test.onmouseup = function(){
		writeLog('mouseup event handler');
		setTimeout(function(){
			writeLog('timeout at mouseup');
		}, 0)
	}
</script>


А в IE setTimeout(…, 1) рискует превратиться в setTimeout(…, 18.2) из-за квантов таймера.

Kolyaj 24.09.2010 12:14

subzey,
http://alljs.ru/articles/timeout/fast-settimeout.html

В первом тесте меняю
setTimeout(arguments.callee, 1);
на
setTimeout(arguments.callee, 0);
Показания не изменяются.

subzey 24.09.2010 12:28

Хм, похоже на то.

Кстати, спасибо за эту тему, побенчмаркал Оперу 10.6 (Win) и получил странные результаты
var startTime = (new Date).getTime();
	var i=0;
	(function(){
		if (++i>100){
			alert((new Date).getTime() - startTime);
		} else {
			setTimeout(arguments.callee, 1E-80);
		};
	})()

Работает значительно быстрее, чем
var startTime = (new Date).getTime();
	var i=0;
	(function(){
		if (++i>100){
			alert((new Date).getTime() - startTime);
		} else {
			setTimeout(arguments.callee, 0);
		};
	})()


Подтвердите, или опревергните, плз.

Kolyaj 24.09.2010 12:32

setZeroTimeout всё равно быстрее :)

Octane 24.09.2010 13:15

setTimeout(function (a, b, c) { alert([a, b, c]); }, 10, "A", "B", "C")

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

Kolyaj 24.09.2010 13:18

Octane,
вобщем использовать это нельзя :) Хотя упомянуть стоит.

x-yuri 24.09.2010 19:56

По поводу передачи контекста и параметров в callback'и: можно добавить два отдельных метода в прототип функции, один для указания контекста, второй для передачи параметров. Например,
foo.of(this).pass(1, 2).defer(100);

вместо
foo.defer(100, this, [1, 2]);

В результате не надо помнить порядок аргументов (по сути получаем именованные аргументы) и не надо пихать эти ctx, args во все остальные функции. Да и просто, у defer как-то многовато обязанностей иначе


По поводу
(function() {
    // Выполняем периодические действия

    setTimeout(arguments.callee, 500);
})();

можно добавить еще один метод, например, periodical и писать как-нибудь вроде
onTimer.periodical(500)();

Как передать в onTimer информацию - см. выше. Помимо вынесения "служебного" кода, передаваемые переменные будут явно обозначены (в отличие от варианта с замыканием)


Цитата:

Сообщение от Kolyaj
пара паттернов у самого ещё не уложились в голове

а что за паттерны?


offtopic: код выглядит контрастно на фоне основного текста...

Kolyaj 24.09.2010 20:22

Цитата:

Сообщение от x-yuri
можно добавить два отдельных метода в прототип функции, один для указания контекста, второй для передачи параметров.

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

Цитата:

Сообщение от x-yuri
В результате не надо помнить порядок аргументов

Но надо помнить, что defer должен быть последний :) А список аргументов IDE по Ctrl+P показывает.

Цитата:

Сообщение от x-yuri
и не надо пихать эти ctx, args во все остальные функции

Я раньше примерно так же рассуждал. Но в результате пришёл к варианту, когда рядом с callback-функцией всегда предаётся контекст его вызова. Гораздо меньше обёрток получается. Я из-за этого в NodeJS страдаю :)

Цитата:

Сообщение от x-yuri
onTimer.periodical(500)();

А остановить его как?

Я стараюсь не вводить сущности без особой необходимости. defer упрощает жизнь, periodical -- нет.

Цитата:

Сообщение от x-yuri
а что за паттерны?

Отсюда.

Цитата:

Сообщение от x-yuri
offtopic: код выглядит контрастно на фоне основного текста...

Ну так он же раскрашен, а текст -- нет :)
Ему может фон добавить, чтоб оттенял.

tenshi 24.09.2010 23:15

var timerId;
element.onmouseover = function() {
    timerId = setTimeout(function() {
        alert(1);
    }, 2000);
};
element.onmouseout = function() {
    clearTimeout(timerId);
};


правильно так:

var timerId;
element.onmouseover = function() {
    if( timerId ) return
    timerId = setTimeout(function() {
        alert(1);
    }, 2000);
};
element.onmouseout = function() {
    timerId = clearTimeout(timerId);
};


или так:

var timerId;
element.onmouseover = function() {
    clearTimeout(timerId);
    timerId = setTimeout(function() {
        alert(1);
    }, 2000);
};
element.onmouseout = function() {
    clearTimeout(timerId);
};

tenshi 24.09.2010 23:44

касательно периодических вызовов, я делаю так:

var clock= Clock().latency( 5000 ).proc( function(){ alert(1) } ).active( true )

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

x-yuri 27.09.2010 21:14

В общем разные у нас приоритеты, Kolyaj :) Я в первую очередь стараюсь убрать детали реализации, чтобы было видно что происходит, а не как это работает. А потом решаю проблемы с производительностью, если такие имеются. Потому что это упрощает мне жизнь. И поэтому количество оберток для меня не аргумент

Цитата:

Сообщение от Kolyaj
Но надо помнить, что defer должен быть последний

не надо
Function.prototype.defer = function( timeout ){
    var f = this;
    return function(){
        var actualFunc = f.of(this);
        actualFunc = actualFunc.pass.apply(actualFunc, arguments);
        setTimeout( actualFunc, timeout );
    }
}


Цитата:

Сообщение от Kolyaj
А список аргументов IDE по Ctrl+P показывает.

да, я помню один раз мне ide-подсказки помогли

Цитата:

Сообщение от Kolyaj
А остановить его как?

например так
function onTimer(){
    ...
    onTimer.stopPeriodical();
}


Цитата:

Сообщение от Kolyaj

а что ты хочешь: обобщить этот код до шаблона проектирования или показать пример использования setTimeout? Просто использование тобой слова "паттерн" вызывает неоднозначные эмоции :)

Цитата:

Сообщение от Kolyaj
Ну так он же раскрашен, а текст -- нет
Ему может фон добавить, чтоб оттенял.

я бы просто css для кода пофиксил

x-yuri 27.09.2010 22:04

и еще...

первое событие timer в любом случае "пропадает"

Array.prototype.deferForEach = function(delay, fn, finish, scope) {
    ...
                if (finish) {
                    finish.call(scope, this);
                }
    ...
    } else {
        finish.call(scope, this);   // finish вызывается, даже если не передан
    }
};


то что нельзя создать два таймера, смещенных по времени - это by design? Просто я не сразу это осознал, несмотря на то, что прочитал "Для каждого интервала создает лишь один экземпляр таймера."

Цитата:

Сообщение от Kolyaj
Я стараюсь не вводить сущности без особой необходимости. defer упрощает жизнь, periodical -- нет.

так твой класс - это по сути periodical в виде класса ;)

with-love-from-siberia 27.09.2010 22:18

.defer можно было бы чуть-чуть переписать, чтобы можно было передавать аргументы не в массиве.
foo.defer(100, this, 1, 2);

вместо
foo.defer(100, this, [1, 2]);

Kolyaj 28.09.2010 09:43

Цитата:

Сообщение от x-yuri
Я в первую очередь стараюсь убрать детали реализации, чтобы было видно что происходит, а не как это работает. А потом решаю проблемы с производительностью, если такие имеются.

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

Вообще, я ж с тобой не спорю. Я раньше точно так же рассуждал, поэтому понимаю твои доводы. Я не говорю, что ты потом будешь рассуждать как-то по другому, просто для меня вариант максимальной абстракции оказался неподходящим. Я ввожу готовые функции, если это
1. Добавляет кроссбраузерности, как например, addEventListener/attachEvent.
2. Сильно упрощает жизнь, как например XMLHttpRequest.


Про Timer: основная его функция -- менеджить много таймеров. Т.е. если у тебя на странице будет несколько десятков независимых анимированных объектов, у каждого из которых свой setTimeout, всё будет слегка поттормаживать. А если сделать один setTimeout, который будет вызывать колбэки из каждого объекта, будет полегче.

Цитата:

Сообщение от x-yuri
так твой класс - это по сути periodical в виде класса

Так что нет :)


Ещё про тот файлик -- там deferForEach. Мне не нравится список аргументов, чтобы описывать его. Он перебирает элементы массива через заданные промежутки времени. Анимация им в две строчки делается.


Цитата:

Сообщение от with-love-from-siberia
.defer можно было бы чуть-чуть переписать, чтобы можно было передавать аргументы не в массиве.

Это сузит область применения. Если у нас есть массив произвольного количества элементов, которые нужно передать в функцию, это придётся делать через ..., неудобно вобщем.


Часовой пояс GMT +3, время: 19:05.