...

пятница, 1 августа 2014 г.

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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

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