Обертывание функций JavaScript позволяет добавлять общую логику к функциям, которыми вы не управляете, например к собственным и внешним функциям. Многим библиотекам JavaScript, например агентам TrackJS, для выполнения своей работы необходимо оборачивать внешние функции. Добавление оболочек позволяет нам прослушивать телеметрию, ошибки и журналы в вашем коде без необходимости явного вызова нашего API.

Вы можете захотеть обернуть функцию, чтобы добавить инструменты или временную логику отладки. Вы также можете изменить поведение внешней библиотеки, не изменяя исходный код.

Обтекание основных функций

Поскольку JavaScript удивительно динамичен, мы можем обернуть функцию, просто переопределив ее чем-то новым. Например, рассмотрим эту упаковку myFunction:

var originalFunction = myFunction;
window.myFunction = function() {
 console.log(“myFunction is being called!”);
 originalFunction();
}

В этом тривиальном примере мы обернули исходный myFunction и добавили сообщение журнала. Но есть много вещей, с которыми мы не справились:

  • Как мы передаем аргументы функции?
  • Как мы поддерживаем объем (значение this)?
  • Как мы получаем возвращаемое значение?
  • Что если произойдет ошибка?

Чтобы справиться с этими вещами, нам нужно немного поумнеть в нашей упаковке.

var originalFunction = myFunction;
window.myFunction = function(a, b, c) {
  /* work before the function is called */
  try {
    var returnValue = originalFunction.call(this, a, b, c);
    /* work after the function is called */
    return returnValue;
  }
  catch (e) {
    /* work in case there is an error */
    throw e;
  }
}

Обратите внимание, что в этом примере мы не просто вызываем функцию, а call передаем ей значение this и аргументы a, b и c. Значение this будет передано из любого места, куда вы прикрепите обернутую функцию, Window в этом примере.

Мы также окружили всю функцию блоком try/catch, чтобы в случае ошибки можно было вызывать пользовательскую логику, повторно вызывать ее или возвращать значение по умолчанию.

Расширенная упаковка функций

Базовый пример упаковки будет работать в 90% случаев, но если вы создаете разделяемые библиотеки, такие как агенты TrackJS, этого недостаточно! Чтобы обернуть наши функции как профессионал, есть несколько пограничных случаев, с которыми мы должны иметь дело:

  • Как насчет необъявленных или неизвестных аргументов?
  • Как нам сопоставить сигнатуру функции?
  • Как мы отражаем свойства функций?
var originalFunction = myFunction;
window.myFunction = function myFunction(a, b, c) { /* #1 */
  /* work before the function is called */
  try {
    var returnValue = originalFunction.apply(this, arguments); /* #2 */
    /* work after the function is called */
    return returnValue;
  }
  catch (e) {
    /* work in case there is an error */
    throw e;
  }
}
for(var prop in originalFunction) { /* #3 */
  if (originalFunction.hasOwnProperty(prop)) {
    window.myFunction[prop] = originalFunction[prop];
  }
}

Есть 3 тонких, но важных изменения. Во-первых (#1), мы назвали функцию. Это кажется излишним, но пользовательский код может проверять значение function.name, поэтому важно сохранить имя при переносе.

Второе изменение (#2) заключается в том, как мы вызывали обернутую функцию, используя apply вместо call. Это позволяет нам пройти через объект arguments, который представляет собой массив-подобный объект всех аргументов, переданных функции во время выполнения. Это позволяет нам поддерживать функции, которые могут иметь неопределенное или переменное количество аргументов.

С apply нам не нужны аргументы a, b и c, определенные в сигнатуре функции. Но, продолжая объявлять те же аргументы, что и исходная функция, мы сохраняем арность функции. То есть Function.length возвращает количество аргументов, определенных в сигнатуре, и это будет отражать исходную функцию.

Последнее изменение (#3) копирует все указанные пользователем свойства из исходной функции в нашу оболочку.

Ограничения

Это тщательное обертывание, но в JavaScript всегда есть ограничения. В частности, сложно правильно обернуть функцию нестандартным прототипом, например конструктором объекта. Это вариант использования, который лучше решается путем наследования.

В общем, изменить прототип функции возможно, но это не очень хорошая идея. Манипулирование прототипами имеет серьезные последствия для производительности и непреднамеренные побочные эффекты.

Уважайте окружающую среду

Обертывание функций дает вам много возможностей для инструментирования и управления средой JavaScript. Вы обязаны мудро пользоваться этой силой. Если вы создаете обертки для функций, обязательно уважайте пользователя и среду, в которой вы работаете. Могут быть другие оболочки, другие прослушиватели, прикрепленные к событиям, и ожидания от API-интерфейсов функций. Действуйте осторожно и не нарушайте внешний код.

JavaScript часто ломается и непредсказуемым образом. TrackJS фиксирует ошибки JavaScript на стороне клиента, чтобы вы могли видеть ошибки и реагировать на них. Попробуйте бесплатно и убедитесь, насколько крута наша упаковка функций.

Первоначально опубликовано на https://trackjs.com.