Декораторы – это функции, которые изменяют другие функции. Декораторы могут быть полезны для применения одинаковых изменений к нескольким функциям.
В этой статье мы рассмотрим, как создавать декораторы в JavaScript и использовать их для изменения методов и классов.
Декораторы методов
Декораторы методов JavaScript – это функции с несколькими параметрами: target
, property
и descriptor
.
Target
– обозначает класс, в котором находится модифицируемый метод, property
– это строка с названием свойства (или метода), подлежащего изменениям, а descriptor
– содержит несколько свойств для описания изменений.
Декоратор может использоваться только в классах, хотя класс JavaScript является просто синтаксическим сахаром над функциями конструктора.
Это относительно новая конструкция, которая отдельными браузерами может не поддерживаться, поэтому, чтобы заставить их работать, может понадобиться Babel или TypeScript.
Например, напишем класс Person
и используем декоратор readonly
, чтобы метод description
этого класса стал доступным только для чтения:
const readonly = (target, property, descriptor) => {
descriptor.writable = false
return descriptor
}
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
@readonly
description() {
return `${this.name} is ${this.age}`
}
}
Здесь в target
будет класс Person
. В Person
есть конструктор и напишем в этом классе метод description
.
Property
– это строка с именем участника класса (свойство или метод), который будет модифицирован. В приведенном выше коде, это будет метод description
класса Person
, поэтому property
содержит строку 'description'
.
У descriptor
есть несколько свойств для описания содержимого декоратора:
value
– здесь будет значение свойстваwritable
– булёвое выражение, влияет на возможность изменения значения (можно ли перезаписывать)enumerable
– булёвое выражение, может ли значение перебираться в циклеconfigurable
– булёвое выражение, обозначает возможность вносить в свойство изменения
В функции декоратора readonly
установим значение false
для descriptor.writable
, поэтому после применения декоратора readonly
к методу description
его значение уже будет невозможно изменить.
Это легко проверить:
let person = new Person('Joe', 10);
console.log(person.description);
person.description = 'foo';
console.log(person.description);
Прежде чем установить значение foo
для person.description
, получим:
ƒ description() {
return `${this.name} is ${this.age}`;
}
Затем попробуем установить значение foo
для person.description
, но оно не изменится и останется прежним:
ƒ description() {
return `${this.name} is ${this.age}`;
}
Посмотрите пример, он подтверждает, что декоратор readonly
не позволил перезаписать содержимое метода description
.
Декораторы классов
Декораторы классов определяются перед описанием класса. Они используются для изменения класса или функции конструктора. Следовательно, они должны возвращать функцию конструктора или новый класс.
Декораторы классов принимают только один параметр – класс, перед которым они определены.
Например, использовать его можно следующим образом:
const addGender = (PersonClass) => {
PersonClass = class Person {
constructor(name, gender) {
this.name = name;
this.gender = gender;
}
}
return PersonClass;
}
@addGender
class Person {
constructor(name) {
this.name = name;
}
}
let person = new Person('Joe', 'male');
console.log(person.gender);
Здесь addGender
– это декоратор класса Person
.
В функции декоратора создаётся и возвращается новый класс, в котором к существующему свойству name
добавляем новое свойство gender
.Теперь его можно использовать следующим образом:
let person = new Person('Joe', 'male');
console.log(person.gender);
Как видите, person.gender
теперь можно прочитать и его значением будет 'male'
.
Это означает, что person
теперь возвращается экземпляром класса, который вернула функция декоратора.
Ещё из декоратора можно вернуть функцию конструктора вместо класса:
const addGender = (PersonClass) => {
PersonClass = function Person(name, gender) {
this.name = name;
this.gender = gender;
}
return PersonClass;
}
@addGender
class Person {
constructor(name) {
this.name = name;
}
}
Тут будет получен аналогичный результат для person.gender
, так:
let person = new Person('Joe', 'male');
console.log(person.gender);
Вот так это будет работать:
Несколько декораторов
Для одного метода или класса можно использовать несколько декораторов. Их надо перечислить перед кодом метода (класса), тогда они будут последовательно выполняться и возвращать результат. Порядок выполнения функций – от ближайшего к классу до самого верхнего в списке декораторов.
const addGender = (PersonClass) => {
console.log('1. addGender');
PersonClass = function Person(name, gender) {
this.name = name;
this.gender = gender;
}
return PersonClass;
}
const addAge = (PersonClass) => {
console.log('2. addAge');
const TmpPersonClass = PersonClass;
PersonClass = function Person(name, gender, age) {
TmpPersonClass.call(this, name, gender);
this.age = age;
}
return PersonClass;
}
@addAge // выполнится последним
@addGender // выполнится первым
class Person {
constructor(name) {
this.name = name;
}
}
const person = new Person('Joe', 'male', '46');
console.log(person.name, person.gender, person.age);
В этом примере сперва выполняется ближайший к описанию класса Person
декоратор @addGender
, а последним – @addAge
.
Подведём итог.
Для изменения методов и классов JavaScript можно создавать декораторы.
Декоратор метода принимает в качестве параметров target
, property
и descriptor
. Декоратор возвращает изменения для метода.
Функция декоратора класса возвращает новую функцию класса или конструктора. Декоратор принимает класс в качестве параметра, при этом нет необходимости изменять переданный класс. Можно просто возвращать новый класс с изменениями.
Для одного метода или класса можно использовать несколько декораторов, которые будут выполняться последовательно.