[Из песочницы] Создание модулей JS

Здравствуйте! В этой статье я хочу рассказать вам о моём подходе к написанию модулей на 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){ //Функция-конструктор для новых блоков

var id = getId (); //id блоков всегда будут уникальны, испортить генератор внешним кодом невозможно this.show = function (){ $(»
», { «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. Я не коснулся темы разрешения зависимостей модулей, т.к. это отдельная тема, заслуживающая целой статьи.

© Habrahabr.ru