Заметки с тегом «pattern» (страница 3)

JSONP для инициализации страницы

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

Предположим, что на сервере есть контроллер, который отдает модель данных в виде JSON. Пусть URL, по которому можно получить эти данные, будет http://example.com/data/?page=1, а сами данные


{sites: [
    {id: 1, name: "Yandex.ru", url: "http://yandex.ru/"},
    {id: 2, name: "Google", url: "http://google.com/"},
    {id: 3, name: "NotesKeeper", url: "http://noteskeeper.ru/"}
]}

Этот URL используется в асинхронном запросе, чтобы получить новую страницу блока. Если его подставить в тег script , то он, конечно же, будет загружен и исполнен, но возникнет синтаксическая ошибка, так как объект не был использован в операции присвоения или при вызове функции.

Добавим в параметры к URL еще один http://example.com/data/?page=1&callback=App.render и модифицируем контроллер на сервере так, чтобы этот параметр (при его наличии) подставлялся в выходные данные


App.render( {sites: [
    {id: 1, name: "Yandex.ru", url: "http://yandex.ru/"},
    {id: 2, name: "Google", url: "http://google.com/"},
    {id: 3, name: "NotesKeeper", url: "http://noteskeeper.ru/"}
]} );

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

Контроллер интерфейса может иметь, например, такую структуру


var App = (function () {
    // скрытые переменные приложения
    return {
        // публичные методы приложения
        render: function (model) {
            // действия, которые можно выполнить до полной загрузки документа
            $(function () {
                // изменения в документе, который выполняются только
                // после его полной загрузки
                App.update(model);
            });
        },
        update: function (model) {
            // обновляем содержимое блока
        }
    };
}());

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

Эта техника и называется JSONP (JSON with padding). Основное применение она нашла в загрузке данных сторонних сервисов, например, таких как Flickr, Yahoo, Google и многих других. Из-за того, что асинхронные запросы для других доменов блокируются политикой безопасности браузера, использование этой техники наряду с iframe является, пожалуй, единственным возможным вариантом.

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

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

Использование prototype

Принципиально отличающимся от замыканий шаблоном написания модулей является объявление полей и методов через prototype.


var Product = function (name, cost) {
    this.name = name;
    this.cost = cost;
};

Product.prototype.draw = function () {
    // Комплексный метод создания представления продукта
    // Доступ к полям экземпляра осуществляется только через this
};

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

К недостаткам же можно отнести:

  • полное отсутствие частных полей (как правило, программисты оформляют такие поля символом «_» перед именем);
  • доступ к полям экземпляра осуществляется только через this, так как это единственная связующая переменная между привилегированными и общими методами.

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


var Product = function (name, cost) {
    // частная переменная
    var id = name + '-' + new Date().getTime();
    // привилегированный метод для доступа к частной переменной
    this.getId = function () {
        return id;
    };
    // привилегированные поля
    this.name = name;
    this.cost = cost;
};

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

Замыкания в 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 файлов

Техника динамической загрузки данных через XHR (в простонародии, AJAX ) очень популярна в современном WEB-е. Но вот динамическую загрузку программных компонент используют пока крайне редко. Компактный модуль ScriptLoader наглядно демонстрирует то, как это может быть просто и функционально.


/**
 * JavaScript loader
 * @param modules {Array} массив имен js файлов
 */
var ScriptLoader = function (modules) {
    this.modules = modules.slice();
    this.queue = new Queue();
    this._init();
};

ScriptLoader.prototype = {
    run: function () {
        // test for header ready
        var head = document.getElementsByTagName("head");
        if (head.length > 0) {
            this.queue.iterate();
        } else {
            setTimeout(arguments.callee, 500);
        }
    },

    _init: function () {
        var i, me = this;
        for (i = 0; i < me.modules.length; i++) {
            (function (name) {
                me.queue.add(function () {
                    var head, script;
                    head = document.getElementsByTagName("head");
                    if (head.length > 0) {
                        head = head[0];
                        script = document.createElement("script");
                        script.src = name;
                        script.type = "text/javascript";
                        script.onload = script.onreadystatechange = function () {
                            if ((!this.readyState || this.readyState == "loaded" || this.readyState == "complete") ) {
                                me.queue.iterate();
                                script.onload = script.onreadystatechange = null;
                                head.removeChild(script);
                            }
                        };
                        head.appendChild(script);
                    }
                });
            })(me.modules[i]);
        }
    }
};

Обратите внимание, что в этом модуле используется Queue.

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


var loader = new ScriptLoader(["file1.js", "file2.js", "http://server.org/js/framework.js"]);
// на этом этапе в очередь могут быть добавлены дополнительные вызовы
loader.queue.add(function () {
    Module1.init();
});
loader.run();

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