Должны ли мы добавлять методы только к прототипам наших объектов?

#javascript

#javascript

Вопрос:

В настоящее время я изучаю Javascript и увидел, что мы можем определять члены объекта в его прототипе, а не в конструкторе объекта, поскольку это делает объекты легче, потому что они не содержат код метода в каждом экземпляре.

Я вижу, как переменные могут работать плохо, если все объекты этого конструктора указывают на одно и то же переменное поле, но методы не должны меняться слишком сильно или вообще, поэтому их совместное использование не проблема.

Есть ли причина не определять методы в прототипе, а не в конструкторе объекта?

Ответ №1:

Одной из причин, по которой можно предпочесть размещать методы в самом экземпляре, было бы обеспечение правильного this контекста при кратком добавлении метода. Правильная this работа — очень распространенная проблема в JavaScript.

Например:

 class Foo {
  i = 0;
  clickHandler = () => console.log(this.i  );
}

const f = new Foo();
window.onclick = f.clickHandler;  

Это общий шаблон с компонентами класса в React. Если вместо этого поместить метод в прототип, иногда он может стать немного уродливее:

 class Foo {
  i = 0;
  clickHandler() {
    console.log(this.i  );
  }
}

const f = new Foo();
window.onclick = () => f.clickHandler();  

Другой распространенный способ справиться с этим — переместить связанный метод из прототипа в экземпляр в конструкторе:

 class Foo {
  i = 0;
  constructor() {
    this.clickHandler = this.clickHandler.bind(this);
  }
  clickHandler() {
    console.log(this.i  );
  }
}

const f = new Foo();
window.onclick = () => f.clickHandler();  

Это причина предпочесть один другому? Это зависит от вас, но следует иметь в виду одно неоспоримое преимущество.

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

1. Почему эта проблема привязки является такой проблемой в React? Ваш второй пример отлично работает в ванильном JS со стандартным eventlistener : window.addEventListener("click", () => f.clickHandler()) .

2. Конечно, но для этого требуется выполнять () => obj.fn() каждый раз fn , когда требуется вызвать (и становится уродливее, когда необходимо передать дополнительные аргументы). Использование вместо этого полей класса (которые, кстати, помещают метод в сам экземпляр) является лишь одним из многих доступных вариантов.

Ответ №2:

Просто идея от меня — есть способ справиться с «правильным» контекстом выполнения, например, обработчиков кликов на уровне прототипа. Рассмотрим следующий пример:

 class AbstractClickHandler {
    
  // the "proper" this is ensured via closure over the actual implementation
  clickHandler() {
    return (...args) => {
      this.handleClick.apply(this, args)
    }
  }

  // no-op in base class
  handleClick() {   
  }
}

class ClickHandler extends AbstractClickHandler {

  constructor(data) {
    super()
    this.data = data
  }

  // actual implementation of the handler logic
  handleClick(arg1, arg2) {
    console.log(`Click handled with data ${this.data} and arguments ${arg1}, ${arg2}`)    
  }
}

const instance = new ClickHandler(42)
const handler = instance.clickHandler()
handler.call(null, 'one', 'two')
  

Таким образом, нет необходимости определять связанный обработчик в каждом соответствующем конструкторе.