Страницы

Поиск по вопросам

четверг, 23 января 2020 г.

Наследование от функции

#javascript #наследование #ecmascript_2015


ES6 позволяет наследоваться от особых объектов. Получается унаследоваться и от функции.
Получившийся объект можно вызывать как функцию. Но как сделать, чтобы при вызове происходило
что-то отличное от вызова пустой функции?

class Smth extends Function {
  // Что здесь сделать
}

(new Smth)() // чтобы тут вернулось не undefined?


PS: Этот вопрос на английском.
    


Ответы

Ответ 1



Взяв в качестве базового вот этот ответ, я немного его доработал и вот что получилось. Дорабатывал я две вещи. Во-первых, передавать функцию в конструктор базового класса - не самое красивое решение; потому я переадресую реальный вызов методу call. Обычно этот метод вызывает основную функцию - но у меня получилось наоборот (сигнатура удачно совпала). Во-вторых, надо обеспечить адекватное значение для this (желательно чтобы это был сам объект-функция, потому что IDE будет думать к контекстных подсказках именно так). При этом надо не потерять реальный this (я его передаю первым аргументом). Ну и плюс надо убрать не имеющую смысла информацию из вывода toString() - никому не интересно видеть там замыкание вида "взять аргументы и передать дальше". Получилось вот так: class ExtendableFunction extends Function { constructor() { super() function f(...args) { return f.call(this, ...args); } Object.setPrototypeOf(f, this.constructor.prototype); return f } call(context, ...args) { throw "Not implemented" } toString() { return this.constructor.name + "(...)"; } }; class HelloWorld extends ExtendableFunction { call() { console.log("Hello, world!", this, arguments) } }; Но мое чувство прекрасного все еще не радо. Вспомните, как часто вы делали обертки над функциями, которые вызывают вложенную функцию через apply. Или как часто вы bindили функцию к объекту... Теперь во всех этих цепочках будет на 1 обертку больше - ведь эти методы будут применяться сначала к f - а потом уже к call. Наконец, если мы уже пишем функтор-обертку, то, вероятно, нам будет проще определять для него не метод call - а метод apply. Поэтому в финальной версии я "спрямил" метод bind, а методы call и apply сделал по умолчанию вызывающими друг друга - чтобы наследник мог переопределить любого. class ExtendableFunction extends Function { constructor() { super() function f(...args) { return f.call(this, ...args); } Object.setPrototypeOf(f, this.constructor.prototype); return f } call(context, ...args) { return this.apply(context, args) } apply(context, args) { return this.call(context, ...args) } bind(context, ...args) { return this.call.bind(this, context, ...args) } toString() { return this.constructor.name + "(...)"; } }; class HelloWorld extends ExtendableFunction { call(context, ..args) { console.log("Hello, world!", this, context, args) } }; class HelloWorld2 extends ExtendableFunction { apply(context, args) { console.log("Hello, world!", this, context, args) } };

Комментариев нет:

Отправить комментарий