12 авг. 2010 г.

Честный undefined

В коде jQuery наткнулся на красивый способ получения «честного» undefined:

(function(undefined) {

// тут мы имеем настоящий undefined

})();

Напомню, что глобальный undefined можно легко переопределить:

window.undefined = 'some_value';

Вышеприведенный же способ позволяет иметь в своем коде настоящий, «защищенный» undefined, неподверженный переопределению извне.

Кроме этого, добавляются «бесплатные» бонусы:

  • undefined становится локальным внутри кода, обращение к нему будет быстрее, чем к глобальному undefined
  • внутри такого кода undefined будет пожат обфускаторами кода, в отличие от глобального

По-моему, очень изящно.

12 нояб. 2009 г.

Tagged EventBus

Развивая идею loose coupling, заложенную в паттерне EventBus, хочу предложить полезное, на мой взгляд, улучшение — тэгирование событий.

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

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

В таком случае, обычные попапы при открытии посылают событие примерно так:

$.eventBus.trigger('popup open');

Попапы с авторизацией так:

$.eventBus.trigger('popup auth open');

Те слушатели, которые хотят слушать открытие любых попапов, включая попапы авторизации, подписываются так:

$.eventBus.bind('popup open', handlerForPopups);

А те, которые слушают открытие только попапов авторизации, так:

$.eventBus.bind('popup auth open', handlerForAuthPopup);

Т.е. если множество тэгов при bind является подмножеством множества тэгов при trigger — то обработчик события срабатывает.

Простейшую реализацию Tagged EventBus можно скачать с code.google. UPD: реализация допилена

22 июн. 2009 г.

Delete vs null

Заметил, что у меня очень долго выполняется очистка памяти от ненужных объектов.

Оказалось, что присваивание null, выполняя примерно ту же функцию очистки памяти что и delete, работает быстрее, в зависимости от браузера:

  • IE6 — примерно в десять раз,
  • IE8 — примерно в четыре раза,
  • Firefox 3 — примерно вдвое,
  • Opera — примерно на 10-20%.

Разница между delete и присваиванием null в том, что, во-первых, после delete значение становится undefined, во-вторых, удаляется не только значение, но и ключ, таким образом, исчезая из циклов вида for in, в отличие от присваивания null.

Если вышеперечисленное не играет роли, то можно смело пользоваться присваиванием null вместо delete.

13 июн. 2009 г.

Думай о ссылках

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

На моей практике, в 90% процентов случаев, можно было, немного подумав, сделать их одной ссылкой, включив изображение в ссылку-подпись и, затем, вынеся его визуально с помощью CSS.

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

Другая распространенная «болезнь» ссылок — ссылки на странице на саму себя. В навигации, в тексте, да все равно где. Не должно быть такого. Любая ссылка должна совершать какое-то действие, переход. Казалось бы, это общеизвестная истина, тем не менее, нарушаемая на подавляющем большинстве сайтов. Если ваш «супердвижок» или «супершаблонизатор» не позволяют сделать этого — не прикрывайтесь этим, переписывайте движок.

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

28 мая 2009 г.

Микропаттерны оптимизации в JavaScript: декораторы функций debouncing и throttling

Декораторы функций позволяют добавить дополнительное поведение функции, не изменяя ее. Сигнатура оригинальной и декорированной функции полностью совпадают.

Debouncing

Если дословно переводить — «устранение дребезга». Такой декоратор позволяет превратить несколько вызовов функции в течение определенного времени в один, причем задержка начинает заново отсчитываться с каждой новой попыткой вызова. Возможно два варианта:

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

Пример использования

Например, у вас есть suggest. Посылать запросы к серверу на каждый keyup расточительно и не нужно. Можно декорировать обработчик, чтобы он срабатывал только после того, как пользователь перестал нажимать на клавиши, допустим, в течение 300 миллисекунд:

function onKeyUp() { ... };

$('input[name=suggest]').keyup($.debounce(onKeyUp, 300));

Или у вас есть один обработчик на несколько событий, и нужно, чтобы если в течение некоторого времени происходит оба события, обработчик срабатывал только на последнем произошедшем событии:

$('input').bind('keyup blur', $.debounce(process, 300));

Throttling

Данный декоратор позволяет «затормозить» функцию — функция будет выполняться не чаще одного раза в указанный период, даже если она будет вызвана много раз в течение этого периода. Т.е. все промежуточные вызовы будут игнорироваться.

Пример использования

Например, на resize окна (или, допустим, на mousemove) у вас срабатывает какой-то тяжелый обработчик. Можно его «затормозить»:

$(window).resize($.throttle(doComplexСomputation, 300));

В итоге, функция будет выполняться не чаще, чем раз в 300 миллисекунд.

Мои реализации в виде jQuery-плагина можно скачать с code.google.

Ps. Навеяно книжкой Николаса Закаса «Professional JavaScript for Web Developers, 2nd Edition», хотя в ней он путает debounce и throttle, называя первое вторым. На ajaxian тоже поднималась эта тема.

20 мая 2009 г.

Загрузка по требованию и jQuery

Несмотря на то, что необходимо минимизировать количество http-запросов, иногда (или часто, в зависимости от задачи) бывает полезно загружать часть «тяжелого» функционала только тогда, когда он действительно понадобится на странице.

У jQuery есть механизм, позволяющий осуществить это — $.getScript, однако, он обладает рядом недостатков:

  • не запоминаются уже загруженные или загружаемые в данный момент скрипты, при повторном запросе опять идет их загрузка.
  • нельзя указать сразу несколько скриптов
  • выключен кэш (к каждому урлу насильно приписываются параметры типа ?_=1242843920520). Зачем это было так жестко сделано, для меня осталось загадкой.
  • у коллбэка нельзя задать контекст (это вообще болезнь коллбэков jQuery, хотя, как недавно рассказал Азат, разработчиками уже делаются шаги в этом направлении).

Я написал небольшой плагин, лишенный вышеперечисленных недостатков:

$.requireScript(url, callback, [context], [options])

Cкачать и попробовать можно с code.google.

13 апр. 2009 г.

Плагин для jQuery, реализующий наследование

Недавно понадобилось для проекта с jQuery реализовать наследование, написал плагин для jQuery, где релизованы идеи, описанные в «Еще раз о наследовании в JavaScript». Плагин находится на code.google — code.google.com/p/jquery-inheritance/

Работает так:

$.inherit([base], methods, [statical])

Пример:

// A — базовый тип
var A = $.inherit(
    {

        // __constructor — специальный метод, вызываемый при создании экземпляра
        __constructor : function(property) {
                this.property = property;
        },

        getProperty : function() {
            return this.property + ' of A';
        },

        getType : function() {
            return 'A';
        }

    },
    {
        // статическое свойство, доступное изнутри как this.__self.staticMember
        staticMember : 'staticA'
    });

// B — тип, наследуемый от A
var B = $.inherit(
    A,
    {

        // перекрытие с вызовом одноименного метода базового класса
        getProperty : function() {
            return this.__base() + ' of B';
        },

        // просто перекрытие
        getType : function() {
            return 'B';
        }

    },
    {
        staticMember : 'staticB'
    });

var instance = new B('value');

console.log(instance.getProperty());
console.log(instance.getType());
console.log(instance.__self.staticMember);

А кто вообще как использует, и использует ли вообще, наследование?