Как функции декораторы работают изнутри?

Добрый день! Пытаюсь разобраться с функциями-декораторами. Понимаю где и как их применять, но не совсем понимаю как они работают «под капотом». Буду благодарен, если кто-то сможет прояснить пару вопросов или направить на нужную тему для прочтения.

Для примера у нас есть функция, которая возвращаем аргумент (число), умноженный на 2. И есть функция-декоратор, которая проверяет аргументы функции на соответствие типу «число».
// Функция, умножающая на 2 
function double (num) {
    return num * 2;
}

// Декоратор
function decorator (func) {
    return function (x) {
        if(isNaN(x)) {
            throw new TypeError('Ошибка! Не число.');
        }
        return func(x);
    };
}

double = decorator(double);
console.log(double(5));


Моменты, которые хотелось бы понять:

1. Каким образом аргумент функции double передаётся в аргумент функции, которую возвращает декоратор?

Если схематически представить функцию decorator, в которую мы передаём double (понимаю, что синтаксически это неверно, просто для визуализации), получится вот так:
function decorator (double(num)) {

    return function (x) {
        if(isNaN(x)) {
            throw new TypeError('Ошибка! Не число.');
        }
        return func(x);
    };
}


Каким образом вот этот аргумент, передаётся сюда?
62421f1a4887e334302946.png

2. Почему не работает прямой вызов функции-декоратора?

// Вот так работает
double = decorator(double);
console.log(double(5)); // 10

// А вот так нет
console.log(decorator(double(5))); // [Function (anonymous)]
  • Вопрос задан
  • 424 просмотра
Пригласить эксперта
Ответы на вопрос 4
sergiks
@sergiks Куратор тега JavaScript
♬♬
Что делает декоратор — получает одну и создаёт новую функцию.

Пример из жизни. Декоратор == мастерская жуликов. Был обычный банкомат (функция). Засунули его в мастерскую злодеев и получили новый банкомат. Новый банкомат выглядит как прежний, но внутри теперь ещё и отправляет данные карты и введённые пин-коды злодеям. При этом свои внешние обычные действия он продолжает выполнять: принимает карту, показывает баланс.

1. Каким образом аргумент функции double() передаётся в аргумент функции, которую возвращает декоратор?

Наоборот: теперь работают с новой функцией, которую вернул декоратор. А уже она, внутри, вызывает double() в которую и передаёт аргумент, с которым её вызвали.

2. Почему не работает прямой вызов функции-декоратора? decorator(double(5))
в таком варианте аргументом в decorator() попадает что? — не сама функция double, а результат её выполнения с аргументом 5.

Наверное, вам пока непривычно, что аргументом функций может быть не только, скажем, число,
а и целая функция. Почувствуйте разницу:
(fn2( fn1 ))(arg)
// fn2 получает аргументом функцию fn1
// возвращает новую функцию, и уже та выполняется
// с аргументом arg

fn2( fn1( arg ))
// fn1 выполняется с аргументом arg
// результат попадает аргументом в fn2
// возвращается результат вызова fn2
Ответ написан
SilenceOfWinter
@SilenceOfWinter
та еще зажигалка...
function double(num) {
    return num * 2;
}
function decorator(innerFn) {
    return function (num) {
        if(isNaN(num)) {
            throw new TypeError('Ошибка! Не число.');
        }
        return innerFn(num);
    };
}

const decoratedDouble = decorator(double);
alert(decoratedDouble(5));


decorator просто оборачивает переданную функцию в другую(`function (x)`) и возвращает её.
decorator(double(5))
не работает потому что ты напрямую вызываешь double, в результате в decorator передается 10(5*2), а не double.

В js есть класс Proxy позволяющий упростить подобные действия.
Ответ написан
@wonderingpeanut
Декораторы это функции высшего порядка, то есть функции, которые принимают в качестве аргумента функцию или возвращают новую функцию.
Они используются для того, чтобы генерализировать часть какого-то функционала. В твоем случае - проверка входящих данных на соответствие какому-то типу.

1. Каким образом аргумент функции double передаётся в аргумент функции, которую возвращает декоратор?

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

const decoratedDouble = decorator(double);
// decoratedDouble это функция, которая принимает аргумент х, проверяет его на соответствие типу 'number'
// и выполняет на нем инструкции, описанные функцией double
// тело функции выглядело бы так:
function decoratedDouble(x) {
  if (isNaN(x)) {
    throw new TypeError(...);
  }

  return x * 2
}


2. Почему не работает прямой вызов функции-декоратора?

Декоратор ожидает аргумент типа function, а ты передаешь в него результат выполнения функции double - число.
decorator(double) // Function
decorator(double)(5) // 10
decorator(double)('Hello World') // TypeError
Ответ написан
Комментировать
delphinpro
@delphinpro Куратор тега JavaScript
frontend developer
а вы вот так перепишите, возможно понятнее будет

// Вот так работает
const decoratedDouble = decorator(double);
console.log(decoratedDouble(5)); // 10
// decoratedDouble это уже другая функция. та, что возвращается из декоратора.
// Просто вы ее перезаписываете в уже существующую переменную.

// А вот так нет
console.log(decorator(double(5))); // [Function (anonymous)]


ну и второй вариант правильнее писать так, т.е. и прямой вызов работает:

console.log(
  decorator(double)(5) // декоратор возвращает функцию, которую надо вызвать
); // 10
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы