Создание модулей JS

в 7:50, , рубрики: javascript, модули

Здравствуйте!

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

Модуль «с нуля»

В моей любимой IDE при создании нового js-файла в окно редактора тут же вставляется вот такой код:

(function (G, U){
    "use strict";
    var $ = G.jQuery,
        bool   = "boolean",
        string = "string",
        number = "number",
        object = "object";

}(this, undefined));

Рассмотрим, что именно происходит в этих нескольких строках.

1. Создание закрытого пространства имён.

Первым делом создаётся закрытое пространство имён путём помещения кода в самовызываемую анонимную функцию. При этом в анонимную функцию передаются два параметра G и U, которым присваиваются соответственно значения this и undefined. Вопрос: чему в данном случае будет равно this? Это зависит от того, с какой платформой идёт работа, но в любом случае это будет глобальное пространство имён, каковым в браузерах, например, является объект window.

2. Использование строгого режима.

Вот эта строка включает строгий режим:

    "use strict";

Я использую строгий режим по многим причинам. Мало того, что его наличие требуется для корректной валидации кода с помощью JSLint, так ещё его использование блокирует многие небезопасные конструкции.
Казалось бы, совершенно невинный кусочек:

for (i = 0; i < items.length; i += 1){
    //Что-то делаем
}

Какие тут могут быть подводные камни? Всё просто: где-то ранее в коде переменная i уже была объявлена, а этот цикл является вложенным:

for (i = 0; i < myArr.length; i += 1){
    //Около 50 строк кода, обычно хватает, чтобы скрыть начало тела цикла
    for (i = 0; i < myArr.length; i += 1){
        //Что-то делаем
    }
}

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

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

Например:

alert("Ошибка доступа: "+ error);

Если переменная error не была ранее объявлена, в обычном режиме пользователь сможет любоваться сообщением "Ошибка доступа: undefined". В строгом режиме эта переменная должна быть как минимум определена.

Что делать, если целевой браузер не поддерживает строгий режим? Ничего. Код, написанный для строгого режима, будет без проблем работать в нестрогом, а интерпретатор JavaScript просто проигнорирует строку "use strict";.

Приватные переменные модуля

После инструкции "use strict";, включающей строгий режим, идёт описание нескольких переменных. Как видите, я следую паттерну «one var statement», так же рекомендуемому к применению. Согласитесь, описанная выше конструкция выглядит не так ужасно, как нижеприведённая:

var $ = G.jQuery;
var bool   = "boolean";
var string = "string";
var number = "number";
var object = "object";

Переменные bool, string, number и object далее я описываю для большего удобства:

if (params.hasOwnProperty("title") && typeof params.title === string){ //где-то в коде
    result.title = params.title;
}

К тому же, при использовании средств типа YUICompressor или Google Closure Compiler имена этих переменных будут сокращены до одно- или двух буквенных:

if (p.hasOwnProperty("title") && typeof p.title === s){
    r.title = p.title;
}

Все объявленные здесь переменные будут приватными и видны только в пределах модуля. Если нам понадобится создать функцию-генератор для уникальных id элементов, сделать это будет очень просто:

(function (G, U){
    "use strict";
    var id = 0,
        PREFIX = "my-library-id-prefix-";

    function getNewId(){
        id += 1;
        return PREFIX + id.toString();
    }
    G.createId = getNewId; //Экспорт функции getNewId() в глобальное пространство имён под именем createId
}(this, undefined));

Но мы можем и не отправлять эту функцию на экспорт, а использовать как служебную внутри модуля:

function Div(params){ //Функция-конструктор для новых блоков <div>
    var id = getId();  //id блоков всегда будут уникальны, испортить генератор внешним кодом невозможно
    this.show = function(){
        $("<div />", {
            "id": id
        });
    }
}
Включение модуля в библиотеку

Возможно, все ваши модули будут оформлены в виде одной библиотеки, именуемой, например, JSui, и все функции должны вызываться вот так:

var newDiv = new JSui.Div({
    width: 200,
    height: 150
});

Можно было бы собрать всё в один файл, расширив единственный объект и экспортировав его в глобальное пространство имён, но отлаживать и редактировать несколько мелких модулей всегда проще, чем один большой. Поэтому мы будем использовать всего лишь одну глобальную переменную и при необходимости расширять её необходимыми свойствами. Я делаю это так:

Файл divs.js:

(function(G, U){
    "use strict";
    var UI= G.JSUI || {};
    //Код модуля
    function Div(){
    ...
    }
    UI.Div = Div;
    G.JSui = UI;
}(this, undefined));

Файл labels.js:

(function(G, U){
    "use strict";
    var UI= G.JSui || {};

    //Код модуля
    function Label(){
    ...
    }
    UI.Label = Label;
    G.JSui = UI;
}(this, undefined));

Некоторые скажут, что присваивание G.JSui = UI; явно лишнее. Но не делайте поспешных выводов! Что, если указанный модуль подключается первым и глобальная переменная JSui ещё не определена? Локальной переменной UI будет присвоен пустой объект {}. Далее в коде модуля мы его расширяем, но экспорта в глобальное пространство имён не происходит до момента вызова строки:

    G.JSui = UI;

Если же глобальный объект JSui уже определён, локальная переменная UI получает ссылку на него и расширяет его новыми свойствами и методами. В этом случае, действительно, указанное выше присваивание будет излишним, однако, не следует забывать тот простой факт, что при этом объект будет передан по ссылке и производительность не пострадает.

P. S. Я не коснулся темы разрешения зависимостей модулей, т.к. это отдельная тема, заслуживающая целой статьи.

Автор: dunmaksim

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js