05. Enforce new

Существует серьёзная опасность, связанная с конструктором функций: если при вызове конструктора функций отсутствует ключевое слово new, то this связывается не с создаваемым объектом, а с глобальным объектом.

function Vehicle(type, wheelsCount) {
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}
// Function invocation
const car = Vehicle("Car", 4);
car.type; // => 'Car'
car.wheelsCount; // => 4
car === window; // => true

Существует два способа гарантировать это:

  1. Не использовать this в конструкторе, а собирать начиная с пустого объекта и возвращать его.

  2. Проверять, что this уже является экземпляром нужного класса в самом начале выполнения конструктора.

Проверка на this (второй способ):

function Vehicle(type, wheelsCount) {
  if (!(this instanceof Vehicle)) {
    throw Error("Error: Incorrect invocation");
  }

  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}

// Constructor invocation
const car = new Vehicle("Car", 4);
car.type; // => 'Car'
car.wheelsCount; // => 4
car instanceof Vehicle; // => true

// Function invocation. Generates an error.
const brokenCar = Vehicle("Broken Car", 3);

new.target property

Свойство new.target позволяет определить была ли функция или конструктор вызваны с помощью оператора new.

  • Если функция была вызвана при помощи оператора new, то new.target возвращает ссылку на конструктор или функцию.

  • При обычном вызове функции new.target имеет значение undefined.

function Foo() {
  if (!new.target) throw "Foo() must be called with new";
  console.log("Foo instantiated with new");
}

Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"

В конструкторе класса, new.target ссылается на конструктор, который был непосредственно вызван new. Это верно и для случая, когда new.target находится в конструкторе родительского класса, а тот в свою очередь вызывается из конструктора дочернего класса.

class A {
  constructor() {
    console.log(new.target.name);
  }
}

class B extends A {
  constructor() {
    super();
  }
}

const a = new A(); // logs "A"
const b = new B(); // logs "B"

Подробнее: MDN: new.target

Last updated