Немножко теории
JavaScript событийно-ориентированный язык программирования, он поддерживает асинхронные, что иногда может приводить к забавным казусам.
var S = 'Вася молодец'; setTimeout(alert(S), 1000); S = 'Вася дурак';
Если вы откроете консоль и запустите этот фрагмент кода, к большому сожалению Васи, вывалится алерт ‘Вася дурак’. Всё дело в том, что код переданный в метод setTimeout
будет выполнен через 1 секунду, а переопределение S произойдёт сразу после создания таймера. А для того, чтобы таких досадных казусов не происходило js поддерживает механизм замыканий closure
— объявление функции внутри функции. В этом случае объявленная функция имеет доступ к данным внешней функции, кроме того, замыкая функцию внутри асинхронной мы можем гарантировать, что следующая строчка будет выполнена за предыдущей.
var S = 'Вася молодец'; setTimeout(function () { alert(S); S = 'Вася дурак'; }, 1000);
Такой код гарантирует, что переопределение переменной произойдёт после того как появится алерт.
Решение
Суть решения моей проблемы заключается в замыкании метода вызывающего setTimeout в setTimeout. В самом простом виде решение выглядит так:
var period = 10; function nonLinearTimer() { setTimeout(nonLinearTimer, period); onNonLinearTimerEvent(); } function onNonLinearTimerEvent() = { // реализация обработчика }
Очевидно onNonLinearTimerEvent()
будет вызываться через промежутки времени равные period
. Но как я уже говорил хотелось бы иметь возможность менять поведение обработчика событий, к тому же хотелось бы иметь возможность отключить таймер, то есть перестать получать от него события. Для начала завернём то что получилось в отдельный объект:
nonLinearTimer = { _period : 0, _onNonLinearTimerEvent : null, } nonLinearTimer.Start = function() { setTimeout(nonLinearTimer.Start, nonLinearTimer._period); nonLinearTimer._onNonLinearTimerEvent(); } nonLinearTimer.setHandler = function(f) { this._onNonLinearTimerEvent = f; } nonLinearTimer.setPeriod = function(p) { this._period = p; }
Теперь мы можем динамически менять поведение обработчика и период использую сеттеры. Но осталась не решена проблема с остановкой таймера. Очевидно что нужно переопределить поведение Start, чтобы она не вызывала сама себя. Для этого определим приватную функцию _empty и при остановке будем подменять вызов Start на вызов _empty:
nonLinearTimer = { _period : 1000, _onNonLinearTimerEvent : null, _empty : function () { }, _mode : null } nonLinearTimer.Start = function() { setTimeout(nonLinearTimer._mode, nonLinearTimer._period); nonLinearTimer._onNonLinearTimerEvent(); } nonLinearTimer.Stop = function() { this._mode = this._empty; } nonLinearTimer.Prepare = function() { this._mode = this.Start; } nonLinearTimer.setHandler = function(f) { this._onNonLinearTimerEvent = f; } nonLinearTimer.setPeriod = function(p) { this._period = p; }
Готово. Теперь вызывая метод Stop
мы можем останавливать таймер.
Я добился своей цели и человечек ходит по карте с разной скоростью как я и хотел. Другой пример использования такого таймера — эмуляция ускоренного движения: достаточно менять период прямо внутри обработчика, например так:
myTimer.setPeriod(500 / tick); tick++;
Пример использования таймера можно посмотреть здесь
Исходный код скачать вот здесь
З.Ы. Прошу строго не судить, если знаете какое-то существующее решение — я его не искал. Задача решалась в рамках хакатона часа в 4 ночи, а потому было принято решение не тратить время на поиск готовых решений, а сразу писать 🙂
ссылка на оригинал статьи http://habrahabr.ru/post/180209/
Добавить комментарий