Это вторая статья из серии «Метапрограммирование в JavaScript и TypeScript». Настоятельно рекомендуется прочитать и первую часть:



В этой статье мы поговорим о декораторах и о том, чем они так хороши!!

Декораторы — неотъемлемая часть метапрограммирования как в JS, так и в TypeScript. Разработчики Python (и не только) хорошо знакомы с этим шаблоном. На данный момент декораторы официально еще не являются частью JavaScript. Пока они находятся на третьем этапе предложения, а это означает, что декораторы рано или поздно станут языковым стандартом.

Однако ждать не нужно! Теперь мы можем использовать декораторы в JS (спасибо Babel) и TypeScript.

В этой статье мы увидим возможности и примеры использования декораторов.

Что такое декоратор?

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

Вот как выглядит декоратор в ES6 (синтаксис):

function myDecorator(target, key, descriptor) {
  //code goese here
}

class Apartment {
  constructor(apt) {
    this.apt = apt;
  }

  @myDecorator
  getFloor() {
    return this.apt.floor;
  }
}
  • @ — это индикатор, показывающий синтаксическому анализатору, что он имеет дело с декоратором.
  • «myDecorator» — имя нашей функции высокого порядка.

Функция myDecorator принимает три параметра:

  • target‹Object›: объект декорируемого метода, в нашем случае класс Apartment.
  • key‹string›: имя метода или свойства, которое мы украшаем, — getFloor.
  • дескриптор‹Дескриптор объекта›: дескриптор нашего метода/свойства. Подробнее об этом читайте в первой статье.

Перейдем к примерам.

Мы начнем с относительно простого сообщения об использовании устаревших методов.

function deprecator(target, key, descriptor) {
  console.warn(`WARNING!! method ${key} is about to be deprecated!!`)
}
class Apartment {
  constructor(apt) {
    this.apt = apt;
  }
  @deprecator
  getFloor() {
    return this.apt.floor
  }
}
// output WARNING!! method getFloor is about to be deprecated!!

Обратите внимание, что сообщение отображается, хотя ни класс «Apartment», ни декорированный метод «getFloor» нигде не используются. Это означает, что декоратор срабатывает, когда код компилируется внутри движка JavaScript.

Что делать, если вам нужно передать параметр декоратору? В нашем случае, чье имя метода следует использовать вместо устаревших? Для этого нужно обернуть декоратор в другую функцию, которая будет возвращать саму функцию-декоратор.

function deprecator(newMethod) {
  return function (target, key, descriptor) {
    console.warn(`WARNING!! method ${key} is about to be deprecated, instead use ${newMethod}`)
  }
}
class Apartment {
  constructor(apt) {
    this.apt = apt;
  }
  @deprecator('newGetFloor')
  getFloor() {
    return this.apt.floor
  }
  newGetFloor() {
    return this.apt.floor
  }
}
// output WARNING!! method getFloor is about to be deprecated, instead use newGetFloor

Теперь наш декоратор может получать принимающие параметры и (цель, ключ, дескриптор) передаются функциям, которые возвращаются нашим декораторам.

Рассмотрим еще один распространенный тип декоратора — регистратор. Нас интересует лог при каждом вызове метода.

let apt = {
  floor: 12,
};

function logger(target, key, descriptor) {
  let originalMethod = descriptor.value;
  descriptor.value = function () {
    console.log(`Method ${key} fired`);
    return originalMethod.apply(this, arguments);
  };
}

class Apartment {
  constructor(apt) {
    this.apt = apt;
  }

  @logger
  getFloor() {
    return this.apt.floor;
  }
}

let myApt = new Apartment(apt);
console.log(myApt.getFloor());

Давайте взглянем на декоратор logger.

function logger(target, key, descriptor) {
  // save original (decorated) method
  let originalMethod = descriptor.value;
  // Replace with our function
  descriptor.value = function () {
    console.log(`Method ${key} fired`); // Log
    // Call the original function
    return originalMethod.apply(this, arguments);
  };
}

Обратите внимание, что наш декоратор имеет доступ к аргументам, переданным декорируемому методу. Их можно анализировать, регистрировать, проверять, просто заменять и многое другое.

Пример декоратора таймера, который измеряет время выполнения метода.

function timer(target, key, descriptor) {
  let originalMethod = descriptor.value;
  descriptor.value = function () {
    const t1 = Date.now();
    const result = originalMethod.apply(this, arguments);
    console.log(`Method ${key} took `, Date.now() - t1);
    return result
  }
}

Некоторые полезные домашние декораторы можно найти здесь:

https://github.com/jayphelps/core-decorators

Промежуточный результат:

Теперь мы знаем, что такое дескрипторы и декораторы. Пришло время перейти к практическому пониманию метапрограммирования.

Если вам понравилась эта статья, нажмите кнопку 👏 аплодировать ♾️ раз.
Следите за обновлениями блога в Twitter и Medium.
Посетите мой веб-сайт и Youtube для видео и публичных выступлений.

Не стесняйтесь задавать вопросы.

Большое спасибо за чтение!