Заметки за февраль 2009 года

Замыкания в JavaScript

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

var Module = function () {
    var private = 100;
    this.getPrivate = function () {
        return private;
    };
};

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

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

Другое применение замыкание находит в обработчиках событий.

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

function drawList(items) {
    var i, li;
    for (i = 0; i < items.length; i++) {
        li = $('<li class="item">' + items[i].name + '</li>').appendTo(root);
        li.click(function () {
            alert(items[i].data);
        });
    }
}

Этот пример не будет работать, как мы ожидаем потому, что переменная i внутри обработчика события click не соответствует порядковому номеру элемента в списке. Значение этой переменной всегда будет равно items.length.

Чтобы пример заработал, обернем обработчик в замыкание.

(function (i) {
    li.click(function () {
        alert(items[i].data);
    });
})(i);

Или можно сразу получить требуемый элемент.

(function (item) {
    li.click(function () {
        alert(item.data);
    });
})(items[i]);

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

Оставте свой комментарий

Конструкторы в JavaScript

В JavaScript нет понятия класс, но есть конструкторы, с помощью которых можно реализовать функцию класса, такую, например, как контейнер переменных и методов. Объекты класса в JavaScript могут иметь скрытые переменные и методы, а так же поддерживают не только классическое наследование, но и другие модели повторного использования кода.

Конструктор представляет собой обычную функцию.

var Module = function () {
};

или

function Module() {
}

Эти две записи тождественны. Я все-таки предпочитаю использовать первый вариант, так как он позволяет объявить функцию с достаточно сложным названием. Например, App.Package1.Module2.

Для создания нового объекта конструктор вызывается через оператор new.

var instance = new Module();

Оператор new меняет значение переменной this внутри конструктора. В отличие от его обычного значения, this будет новым объектом. Тело конструктора обычно инициализирует поля объекта. По завершению конструктор вернет этот новый объект, если точка выхода не будет явно переопределена через оператор return.

Некоторый смущающий момент заключается в том, что если конструктор будет вызван без оператора new, то this уже не будет новым объектом, а будет ссылаться на window!

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

var Module = function () {
    var me = {};
    return me;
};

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

Оставте свой комментарий

Система оповещения модулей

Приятно наблюдать за тем как развиваются языки программирования, за появлением различных фреймворков и паттернов. Так в моем любимом JavaScript в последнее время появилась хорошая тенденция оформлять код в виде модулей (классов). Это обеспечивает и лучшую читаемость программ и повышает повторное использование кода.

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

/**
 *  Observable Object
 *  Just call it in constructor of your class (delegate pattern)
 *  Ex: var me = Observable(); // create new object
 *      or
 *      Observable(me);        // extend existing object
 */
var Observable = function (target) {
    // callback holder
    var observers = {};
    // create new object if needed
    target = target || {};

    target.attachObserver = function (eventType, callback) {
        if (!observers[eventType]) {
            observers[eventType] = [];
        }
        observers[eventType].push(callback);
        return this;
    };

    target.detachObserver = function (eventType, callback) {
        var a, i;
        if (eventType) {
            if (observers[eventType] && callback) {
                a = observers[eventType];
                for (i = 0; i < a.length; i++) {
                    if (a[i] === callback) {
                        a.splice(i, 1);
                        break;
                    }
                }
            } else {
                delete observers[eventType];
            }
        } else {
            observers = {};
        }
        return this;
    };

    target.notify = function () {
        var a, i, handler, data = [];
        // copy notification arguments
        Array.prototype.push.apply(data, arguments);
        a = observers[data.shift()];
        if (a && a.length > 0) {
            for (i = 0; i < a.length; i++) {
                handler = a[i];
                if (typeof handler === 'string') {
                    handler = this[handler];
                }
                if (handler instanceof Function) {
                    handler.apply(this, data);
                }
            }
        }
        return this;
    };

    return target;
};

Этот компонент при инициализации создает в указанном объекте 3 метода:

  • attachObserver — добавляет колбек для определенного события
  • detachObserver — удаляет один или все колбеки для определенного события
  • notify — инициализирует событие, при этом вызываются все зарегистрированные колбеки с указанными аргументами

Все указанные методы поддерживают цепочный вызов.

var FieldObject = function (root) {
    var me = Observable(this);

    $(root).append('<div class="field"><input type="text" value="" /></div>');
    $('div.field input', root).change(function () {
        me.notify('valueDidChange', this.value);
    });

    return me;
};

FieldObject($('body'))
    .attachObserver('valueDidChange', function (data) {
        alert(data);
    });

В этом примере я создал простой программный компонент FieldObject, которой добавляет на страницу текстовое поле, а затем подписался на событие valueDidChange, которое случается при обновлении этого текстового поля. Весь фокус в том, что я могу не знать, как устроен этот объект, и откуда получить значение его текстового поля. Все что мне нужно – это отреагировать на изменение поля.

Оставте свой комментарий

Перезагрузка фоновых изображений в IE6

Несколько раз сталкивался с одной неприятной проблемой при отображении страниц в Internet Explorer 6. Эффект проявлялся на сложных страницах со всевозможным «хитрым» позиционированием и оформлением блоков. Я заметил, что браузер при обновлении страниц постоянно перегружает фоновые изображения.

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

Решением этой проблемы послужил следующий трюк.

html {
    filter: expression(document.execCommand("BackgroundImageCache", false, true));
}

Для любителей валидировать CSS это правило можно заключить в условный комментарий только для IE.

<!--[if IE]><link rel="stylesheet" type="text/css" media="all" href="/css/ie.css"></link><![endif]-->

Так же можно использовать альтернативный вариант в виде JavaScript.

try {
    document.execCommand("BackgroundImageCache", false, true);
} catch (err) {}
Оставте свой комментарий

Sticky footer — позиционирование «подвала» страницы

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

Достаточно давно в одном из блогов был описан стабильный метод для решения этой задачи, не потерявший актуальности и по сей день.

html, body {
    height: 100%;
}
#wrapper {
    position: relative;
    min-height: 100%;
}
* html #wrapper {
    height: 100%;
}
#content {
    padding-bottom: 120px;
}
#footer {
    position: relative;
    margin: -100px auto 0 auto;
}

Макет страницы:

<html>
    <body>
        <div id="wrapper">
            <div id="content"></div>
        </div>
        <div id="footer"></div>
    </body>
</html>

Ключевым правилом, позиционирующим футер, является отрицательное значение margin-top. Когда контента мало, он естетсвенным образом, прикрепляется к нижней границе окна из-за действия height: 100% у тегов html и body. В случае, когда размер контента превышает размер окна, футер оказывается поверх блока-обертки, но padding-bottom заранее резервирует место для него.

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

Комментарии к заметке: 4