JavaScript: игнорировать разницу между конструкторами функций и классов - PullRequest
0 голосов
/ 30 апреля 2020

Как можно относиться к конструкторам Function & Class одинаково в отношении Function.prototype. [Call и | или apply]?

То есть может быть доступна абстракция для обеспечения того же ожидаемого поведения для class как с function?

Тест

var context = { datum0: true };
var result1 = (function Class() { this.datum1: true; return this; }).call(context);
var result2 = (class Klass { datum1 = true; }).call(context);

assert(result1).equals(context);  // true
assert(result2).equals(context);  // true
assert(context).has('datum0');  // [obviously] true
assert(context).has('datum1');  // true
assert(context).has('datum2');  // true

Мотивация

Иногда идеально позволить конструкции определить поведение объекта, где this не является экземпляром самого класса, в отличие от Mixin. Это просто, когда все конструкторы могут быть приняты как конструкторы функций. Однако, если конструктор класса должен определять поведение, скажем, на new Proxy(context, handler), объект-контекст (this) в пределах области действия экземпляра всегда является экземпляром данного класса. Следующий код демонстрирует такие неприятности и многое другое:

class Class {
  datum1 = true;
  constructor() {
    document.body.addEventListener('click', this.handleClick, false);  // this !== context
  }
  handleClick = (e) => {...};
}

var Klass = function Klass() {

  function handleClick(e) {...}

  this.datum2 = true;
  this.handleClick = handleClick;
  document.body.addEventListener('click', this.handleClick, false);  // triggers proxy. this === context

  return this;
};

var context = new Proxy({ datum0: true, target: Class || Klass }, {
  get(object, key, receiver) {
    var result = Reflect.get(object.target.prototype, key, receiver);
    if (result && result.call) return new Proxy(result, {  // if function, return function-proxy with trigger
      apply(fn, thus, args) {
        var result = Reflect.apply(fn, thus, args);
        console.log(`${key} is a function and was invoked`);  // mock notifier
        return result;
      }
    });
    return result;
  }
});

var decorated = Class.call(context);  // throws "constructor 'Class' cannot be invoked without 'new'"
var decorated = Klass.call(context);  // decorated === context === true

В этом примере context получает данные и поведение от Klass, но не от Class. Аналогично - когда мы передаем this.handleClick в addEventListener - прокси-геттер замечает, что handleClick является функцией, возвращает прокси для handleClick, и то, что передается в качестве слушателя, это то, что с триггером на сообщите нам, что метод был вызван. Это полезно, если мы хотим проверить состояние объекта и запустить шаблон refre sh. Однако - в Class - поскольку объект-контекст всегда является экземпляром конструктора, мы не можем дублировать структуру с контекстом, который мы хотим вместо этого использовать. Более того, если мы хотим позволить разработчикам использовать функциональные конструкторы (скажем, для совместимости), наша нормализация для модулей и компонентов сводится к наименьшему общему знаменателю - никогда не использовать вызов / применять на любой зарегистрированной способности.

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

class Class {
  @trigger() handleClick(e) {...}  // [hypothetically] returns a function-proxy for handleClick
}

... однако это все еще не охватывает все мои базы - по крайней мере, те, которые я не обозначил в этом вопросе.

Вопрос

Есть ли способ поменять контекст объекта (this) на класс-конструктор? (Возможно, используя Class.constructor.call или иным образом)

...