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