[Из песочницы] Создание модулей 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){ //Функция-конструктор для новых блоков
Файл 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. Я не коснулся темы разрешения зависимостей модулей, т.к. это отдельная тема, заслуживающая целой статьи.