Если вы помните, замыкания JavaScript являются чрезвычайно мощной концепцией в языке. Используя довольно специфические правила определения содержания JavaScript, замыкания являются способом создания частных переменных и функциональных возможностей для объекта. Шаблон модуля основан на этой функции.
Краткий обзор возможностей в JavaScript может быть в порядке. Область действия — это средство, с помощью которого программисты ограничивают видимость и время жизни своих переменных и параметров. Без области видимости все переменные были бы глобальными и видимыми везде. В C # область действия вводится в фигурные скобки: вы можете объявить новую переменную внутри некоторого кода в фигурных скобках — блок— и эта переменная не будет видна за пределами блока и фактически будет уничтожена, как только выполнение достигнет закрывающей скобки. Эти фигурные скобки могут быть фигурными скобками, окружающими код в методе, или они могут быть фигурными скобками, обозначающими блок для оператора if или for, и так далее. Суть области видимости в C # заключается в том, чтобы: объявить переменную внутри блока, и она не будет видна вне фигурных скобок, окружающих блок. (Для получения полной информации о области видимости в C # вы должны прочитать раздел 3.7 языка программирования C # (четвертое издание) Хейлсберга и др. Существует примерно восемь страниц обсуждения области.)
В JavaScript есть одно основное правило: функции определяют область видимости. Если вы объявляете переменную в функции, она видна в любом месте внутри этой функции, в том числе внутри любых вложенных функций, даже до того, как она была объявлена. Это не видно за пределами функции. Поскольку вложенные функции также являются переменными, они также будут видны в любом месте включающей функции, даже до того, как они будут объявлены . По сути, JavaScript поднимает все переменные в верхнюю часть функции включения, чтобы они были объявлены до того, как им будет присвоено значение. Это похоже на лексическую структуру Pascal с его блоками var, за исключением того, что этот подъем происходит автоматически в JavaScript.
Обратите внимание, что под вложенными функциями я подразумеваю функции, вложенные в лексическом смысле, а не в смысле выполнения. Другими словами, если функция A вызывает функцию B, это не означает, что B может внезапно «увидеть» переменные, объявленные в A. Для этого B должен быть закодирован в исходном коде для A. Это лексическое вложение, которое обеспечивает объем.
Давайте посмотрим на это в коде:
var outerFunc = function (paramOuter) { // Visible: paramOuter, varOuter // nestedFunc1, nestedFunc2 var varOuter = 42; var nestedFunc1 = function (paramNested1) { // Visible: paramOuter, varOuter // nestedFunc1, nestedFunc2 // paramNested1, varNested1, // nestedFunc1inner var varNested1 = false; var nestedFunc1inner = function () { // Visible: paramOuter, varOuter // nestedFunc1, nestedFunc2 // paramNested1, varNested1, // nestedFunc1inner, inner1 var inner1 = "hello"; }; }; var nestedFunc2 = function (paramNested2) { // Visible: paramOuter, varOuter // nestedFunc1, nestedFunc2 // paramNested2, varNested2 var varNested2 = false; }; };
Это не совсем пример некоего назидательного кода, но он все понял. Если вы посмотрите на функцию nestedFunc1inner, то увидите, что все переменные из внешней функции находятся в области видимости, а также все переменные из непосредственной родительской функции.
Отличная вещь в этих правилах определения области видимости заключается в том, насколько хорошо они соответствуют замыканиям. Замыкание инкапсулирует среду функции, так что она может продолжать существовать как объект даже после завершения функции. В C # вы видите это больше всего с лямбда-выражениями и анонимными делегатами, но с JavaScript вы видите это повсюду. JQuery, по сути, одно гигантское закрытие.
Давайте предположим, что нам нужно написать объект секундомера, который мы будем использовать для определения временных отрезков нашего кода JavaScript. У объекта должен быть метод start (), который запускает тиканье секундомера, метод stop (), чтобы остановить часы (и который также должен возвращать количество прошедших миллисекунд). Без замыканий мы, вероятно, напишем что-то вроде этого:
var stopwatch = { startTime: -1, now: function () { return +new Date(); }, start: function () { this.startTime = this.now(); }, stop: function () { if (this.startTime === -1) { return 0; } var elapsedTime = this.now() - this.startTime; this.startTime = -1; return elapsedTime; } };
Обратите внимание, что у меня есть вспомогательный метод под названием now () (эта конструкция + new Date () выглядит слишком странно в реальном коде — что он делает снова?), А также внутреннее поле, которое записывает время начала секундомера. К сожалению, хотя этот объект хорошо инкапсулировал эти два члена, они общедоступны и полностью видимы Мы их не спрятали. Пользователь объекта секундомера может получить к ним доступ, изменить переменную, заменить их, что угодно. Кроме того, я ненавижу это говорить, но все эти обязательные ссылки не наполовину запутывают код и делают его многословным.
Введите шаблон модуля . С помощью шаблона модуля мы создаем анонимную функцию и выполняем ее автоматически. Функция вернет объект, который будет нашим объектом секундомера. Давайте посмотрим на этот шаг за шагом. Сначала мы создаем внешнюю анонимную функцию и автоматически выполняем ее. Сейчас у нас будет функция, возвращающая пустой объект.
var stopwatch = (function () { // other code to be added here return { // define some fields }; }());
Пока что ничего особенного, я уверен, вы согласитесь. Теперь начинается самое интересное. Давайте добавим обратно исходный код секундомера, изменив его так, чтобы он имел смысл как локальные переменные и вложенные функции.
var stopwatch = (function () { var startTime = -1, now = function () { return +new Date(); }, start = function () { startTime = now(); }, stop = function () { if (startTime === -1) { return 0; } var elapsedTime = now() - startTime; startTime = -1; return elapsedTime; }; return { // define some fields }; }());
Как вы можете видеть, все эти раздражающие ссылки исчезли, так как больше нет включающего объекта. Вместо этого мы можем положиться на область действия функции для разрешения ссылок на переменные. Например, в функции start () мы можем ссылаться на локальную переменную startTime внешней анонимной функции. В этом отношении то же самое относится к функции stop (). Последний шаг — убедиться, что у возвращаемого объекта есть два обязательных метода, start () и stop ().
var stopwatch = (function () { var startTime = -1, now = function () { return +new Date(); }, start = function () { startTime = now(); }, stop = function () { if (startTime === -1) { return 0; } var elapsedTime = now() - startTime; startTime = -1; return elapsedTime; }; return { start: start, stop: stop }; }());
Код, который создает новый возвращаемый объект, выглядит немного странно, пока вы не прочитаете его как «у этого нового объекта есть свойство stop, значением которого является внутренний объект функции stop () и т. Д.»
Возвращенный объект использует замыкание, сформированное анонимной функцией. Два свойства (метода) объекта будут вызывать вложенные функции внутри замыкания, а те, в свою очередь, будут использовать локальную переменную внешней функции startTime. По сути, мы создали несколько закрытых членов: локальную переменную и функцию now (). Эти два члена недоступны за пределами закрытия.
Следует признать, что написанный код предполагает, что наш объект секундомера будет одноэлементным; в конце концов, мы автоматически выполняем анонимную функцию, и это трудно сделать дважды подряд. Если вам нужно несколько секундомеров, то лучше всего назначить эту анонимную функцию переменной с именем createStopwatch, и затем вы можете вызвать ее ad nauseam, чтобы создать столько секундомеров, сколько захотите.
var createStopwatch = function () { // same code as before return { start: start, stop: stop }; }; var stopwatch1 = createStopwatch(); var stopwatch2 = createStopwatch();
В следующий раз мы рассмотрим, как дополнить объект, созданный шаблоном модуля.