var obj = новый CallableObject (); obj (аргументы);

Вызываемый объект - это структура данных, которая ведет себя и как объект, и как функция. Вы можете получать доступ и назначать свойства obj.bar, вызывать методы obj.foo(), а также напрямую вызывать объект obj(), как если бы это была функция.

Прямой вызов подобен вызову метода obj, который имеет доступ к свойствам объекта через свой this контекст.

Если у вас есть опыт работы с Python, вы поймете, что существует встроенный протокол Python, использующий метод класса __call__. К любому методу, назначенному __call__, можно получить доступ как obj.__call__() или как obj().

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

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

Соревнование

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

Для этого нам нужно наследовать от конструктора Function, который наследуется от Object и позволяет нам создавать как объект, так и динамическую функцию.

Наше главное препятствие - дать объекту функции ссылку на самого себя.

Чтобы иметь ссылку на метод _call, функциональная часть нашего объекта функции, сгенерированная нашим классом / конструктором Callable, должна иметь ссылку на себя.

Решения

Мы хотим создать расширяемый Callable класс, который поддерживает правильное и правильное наследование в JavaScript и позволяет нам вызывать объекты, которые он конструирует, как функции со ссылкой на самих себя, перенаправляя эти вызовы на переопределяемый метод _call.

Связанный путь

Попробуйте на repl.it

Поскольку мы наследуем от Function, мы можем создавать динамические функции из строк, используя super в нашем конструкторе. Таким образом, строка, которую мы передаем super, будет телом нашей функции. Мы хотим, чтобы эта функция имела доступ к собственному объекту и вызывала метод _call, передавая свои аргументы. Делаем это с помощью привязки.

Метод привязки позволит нам установить this контекст функции на все, что мы захотим, заключив эту функцию в прозрачную связанную функцию. Итак, мы привязываем функцию к самой себе с помощью this.bind(this).

Теперь наш вызываемый объект имеет ссылку на себя, за исключением того, что объект, который мы возвращаем из нашего конструктора, возвращаемый this.bind, является обернутой версией нашего исходного объекта. Таким образом, все наши свойства будут прикреплены к этому новому объекту функции в оболочке, и наша функция будет иметь ссылку на старый объект без оболочки, переданный для привязки.

Простое решение - прикрепить ссылку на новый обернутый объект на старом объекте как _bound. А тело нашей функции в строке, переданной в super, просто вызывает _call, используя ссылку this._bound.

Плюсы

  • Не полагается на устаревшие или современные функции.
  • Нет необходимости изменять прототипы.

Минусы

  • Требуется упаковка объекта функции в связанную функцию.

Путь Callee

Попробуйте на repl.it

Мы снова используем вызов super для создания динамической функции, но в этот раз мы получаем ссылку на саму функцию, используя преимущества другой неявной переменной внутри функции, объекта arguments.

У объекта arguments есть свойство arguments.callee, которое является ссылкой на вызываемую функцию. Мы используем эту ссылку в качестве первого аргумента для apply, который связывает контекст функций this с самим собой.

Итак, внутри тела функции, строки, переданной в super, нам нужно только вызвать метод _call на arguments.callee.

Плюсы

  • Очень простой.
  • Нет необходимости изменять прототипы.

Минусы

  • arguments и arguments.callee недоступны в ‘strict mode’, подробнее см. MDN.
  • В современном коде следует избегать arguments для оператора спред / остаток.

Закрытие и путь прототипа

Попробуйте на repl.it

Здесь вместо создания динамической функции с super мы отбрасываем объект функции, созданный конструктором (объект this), и заменяем его закрытием, возвращая его вместо this из constructor.

Замыкание также является функциональным объектом и может ссылаться на себя в своем теле через переменную closed over closure. Мы используем ссылку closure для перенаправления вызовов его методу _call.

Но мы разорвали цепочку прототипов, заменив this на closure, поэтому мы повторно присоединяем прототип конструктора к closure, используя Object.setPrototypeOf и new.target (который является ссылкой на конструктор), чтобы получить прототип.

Вы можете использовать this.constructor.prototype вместо new.target.prototype, но тогда вы должны сначала вызвать super, чтобы создать объект this, что расточительно.

Плюсы

  • Не требует обертывания возвращаемого объекта Proxy или bind.

Минусы

  • Требуется доработка прототипов.
  • Изменение прототипов происходит медленно и имеет другие побочные эффекты, см. MDN.

Прокси-путь

Попробуйте на repl.it

Используя Прокси, мы можем перехватывать вызовы функции, используя ловушку apply, и перенаправлять ее на другую функцию. Ловушка apply дает нашему вызываемому объекту ссылку на самого себя в качестве аргумента target.

Итак, мы создаем класс, который наследуется от Function, Callable, обертывая вызываемые объекты, созданные в Proxy, перехватывая любые вызовы, сделанные к этим объектам, и перенаправляя их методу _call на самом объекте, используя ссылку target.

Плюсы

  • Простой и понятный способ перехвата вызовов и их перенаправления.
  • Нет необходимости изменять прототипы.

Минусы

  • Требует обертывания объектов, созданных Callable, в Proxy.
  • Небольшое снижение производительности при использовании обработчиков Proxy.

Что мы узнали

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

Ссылки:

Https://stackoverflow.com/questions/36871299/how-to-extend-function-with-es6-classes/40878674#40878674