Статьи

JavaScript для разработчиков на C #: Шаблон модуля (часть 1)

Если вы помните, замыкания 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();

В следующий раз мы рассмотрим, как дополнить объект, созданный шаблоном модуля.