Статьи

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

В прошлый раз я говорил о простом шаблоне модуля . Здесь вы создаете функцию, которая возвращает объект с поведением и состоянием, и это поведение и состояние реализуются (и делаются закрытыми) с помощью замыкания. Мы показали это, используя шаблон модуля для создания объекта секундомера.

Давайте теперь посмотрим, как мы можем расширить этот объект секундомера, добавив средство, чтобы иметь время круга. Это дает нам возможность использовать один и тот же секундомер для определения последовательности действий, требующих много времени, вместо того, чтобы каждый раз создавать новый секундомер. Единственное предостережение в том, что мы не можем изменить код, который мы уже написали.

Тогда мы будем предполагать, что у нас есть новый объект секундомера, созданный из предыдущего кода. Нам нужно добавить метод lap () для записи времени с начала или с последнего круга. Нам также нужен метод reportLaps (), который будет возвращать массив времени круга. Один из способов сделать это — создать новый объект-обертку (назовите его, скажем, lapwatch), который использует уже созданный секундомер внутри себя в качестве делегата. Другими словами, новый объект lapwatch будет делегировать все моменты времени внутреннему секундомеру, но сохранит свое время круга. Прекрасно выполнимо, но вряд ли расширяет оригинальный объект секундомера.

Что мы будем делать, так это снова применить шаблон модуля, но на этот раз не создавать новый объект. Вместо этого мы расширим существующий объект. Итак, первое изменение — передать существующий объект нашей анонимной функции.

(function (sw) {

  // some code that operates on sw

}(stopwatch));

У нас есть другая автоматически выполняемая анонимная функция, но на этот раз она принимает один параметр: объект секундомера. Внутри функции параметр для удобства известен как sw. Мы предполагаем, что пока неписаный код будет модифицировать sw (и, следовательно, внешний секундомер). Вы также можете вернуть sw и переназначить его на секундомер, если хотите, так же, как мы это делали ранее.

Теперь мы можем написать код, который обеспечивает и сообщает время круга.

(function (sw) {
  var laptimes = [];

  sw.lap = function () {
    laptimes.push(sw.stop());
    sw.start();
  };

  sw.reportLaps = function () {
    var laps = laptimes;
    laptimes = [];
    return laps;
  };
}(stopwatch));

Как видите, у нас есть локальный массив для хранения времени круга, и мы добавляем два новых метода к существующему объекту. Функция формирует другое замыкание и предоставляет новую закрытую переменную. Метод lap () является чем-то вроде хака: поскольку я оговорил, что мы не можем изменить исходный объект, и поскольку у нас нет доступа к переменной startTime, мне пришлось остановить секундомер, чтобы получить истекшее время, а затем сразу запустить снова. Возможно, лучшим решением было бы добавить метод Peek () к исходному коду, просто чтобы мы могли видеть текущее истекшее время без остановки секундомера.

И вот некоторый фиктивный код, который выполняет этот расширенный секундомер:

var i;
stopwatch.start();
for (i = 0; i < 100000; i++);
stopwatch.lap();
for (i = 0; i < 200000; i++);
stopwatch.lap();
for (i = 0; i < 300000; i++);
stopwatch.lap();
console.log(stopwatch.reportLaps());

Этот шаблон обычно известен как Tightly Augmented Module Pattern. Мы передаем текущий объект, и функция формирует еще одно замыкание над ним, чтобы изменить его. Чтобы этот код работал, мы * должны * сначала объявить исходный код, а затем этот код. Если они находятся в разных файлах исходного кода (обычный случай), мы должны сначала объявить файл кода секундомера, а затем второй расширенный код секундомера. Таким образом, интерпретатор JavaScript выполнит код в правильном порядке; код дополнения не будет запущен для неопределенной переменной.

Небольшая модификация, которую мы не можем показать на нашем примере с секундомером, это Loosely Augmented Module Pattern. С помощью этого шаблона мы обычно создаем некоторый служебный объект или пространство имен, которое содержит целый ряд других объектов, которые выполняют некоторую работу. Эти другие объекты не требуют и не взаимодействуют друг с другом. Например:

var jmbNamepace = (function ($j) {
  $j.date = { ... };
}(jmbNamespace || {}));

--- 

var jmbNamepace = (function ($j) {
  $j.regex = { ... };
}(jmbNamespace || {}));
 
---

var jmbNamepace = (function ($j) {
  $j.url = { ... };
}(jmbNamespace || {}));

Каждый из этих трех сегментов кода может быть запущен перед любым другим, и каждый может быть опущен, если это необходимо. Если бы они были в разных исходных файлах, эти исходные файлы могли быть загружены в любом порядке, и только те, которые были необходимы, могли быть загружены. Магия в выражении jmbNamespace || {}. Это говорит: «оцените выражение как равное jmbNamespace, если оно определено, в противном случае оцените как пустой объект».

Возвращаясь к нашему примеру с секундомером, обратите внимание, что в нем что-то глючит. Если бы я вызвал stop () для расширенного секундомера в конце последнего «круга», время для него не будет записано в массиве времен круга. Мы должны учесть эту возможность. Способ сделать это через переопределение : мы должны переопределить поведение stop (), если мы используем секундомер для временных кругов. Вот как это сделать (и обратите внимание, что я изменил определение анонимной функции, чтобы она возвращала расширенный объект секундомера, чтобы показать, что это в равной степени допустимое применение шаблона Tightly Augmented Module):

var stopwatch = (function (sw) {
  var laptimes = [];
  var oldStop = sw.stop;

  sw.stop = function () {
    laptimes.push(oldStop());
  };

  sw.lap = function () {
    sw.stop();
    sw.start();
  };

  sw.reportLaps = function () {
    var laps = laptimes;
    laptimes = [];
    return laps;
  };

  return sw;
}(stopwatch));

Первое, что происходит, это то, что мы копируем объект функции, на который ссылается метод stop () секундомера, и сохраняем его в локальной переменной oldStop. (Радости функций первого класса или «функции являются объектами»!) Затем мы заменяем метод stop () новым, который помещает время последнего круга на массив, и вызываем старую функцию остановки, чтобы получить это время последнего круга. , Метод lap () также должен измениться: теперь мы можем просто вызвать метод stop () объекта секундомера, чтобы выполнить нашу работу, поскольку он теперь перенаправлен через наше переопределение. Все эти махинации, конечно, скрыты от глаз внутри крышки.

В следующий раз мы закроем этот мини-сериал по шаблону модуля с некоторыми заключительными функциями.

( Часть 1 этой серии.)