Object.prototype.constructor

Властивість даних constructor (конструктор) примірника Object повертає посилання на функцію-конструктор, що створила цей об'єкт-примірник. Зверніть увагу: значення цієї властивості – посилання на саму функцію, а не рядок, що містить назву цієї функції.

Примітка: Це властивість об'єктів JavaScript. Метод constructor в класах описано на окремій сторінці довідника.

Значення

Посилання на функцію-конструктор, що створила цей об'єкт-примірник.

Атрибути властивості Object.prototype.constructor
Записна так
Перелічувана ні
Конфігуровна так

Примітка: Ця властивість усталено створюється на властивості prototype кожної функції-конструктора й успадковується всіма об'єктами, створеними таким конструктором.

Опис

Усі об'єкти (за винятком null-прототипних об'єктів) мають властивість constructor на своєму [[Prototype]]. Об'єкти, створені за допомогою літералів, також мають властивість constructor, що вказує на відповідний типовий конструктор для об'єкта – наприклад, літерали масивів створюють об'єкти Array, а літерали об'єктів створюють прості об'єкти.

const o1 = {};
o1.constructor === Object; // true

const o2 = new Object();
o2.constructor === Object; // true

const a1 = [];
a1.constructor === Array; // true

const a2 = new Array();
a2.constructor === Array; // true

const n = 3;
n.constructor === Number; // true

Зверніть увагу: constructor зазвичай приходить з властивості prototype конструктора. Якщо присутній довший ланцюжок прототипів, то зазвичай можна очікувати того, що кожний об'єкт у ланцюжку матиме властивість constructor.

const o = new TypeError(); // Успадкування: TypeError -> Error -> Object
const proto = Object.getPrototypeOf;

Object.hasOwn(o, "constructor"); // false
proto(o).constructor === TypeError; // true
proto(proto(o)).constructor === Error; // true
proto(proto(proto(o))).constructor === Object; // true

Приклади

Виведення конструктора об'єкта

Наступний приклад створює конструктор (Tree) і об'єкт новоствореного типу (theTree). Потім цей приклад виводить властивість constructor об'єкта theTree.

function Tree(name) {
  this.name = name;
}

const theTree = new Tree("Червоне дерево");
console.log(`theTree.constructor – це ${theTree.constructor}`);

Цей приклад має наступний вивід:

theTree.constructor – це function Tree(name) {
  this.name = name;
}

Присвоєння властивості constructor об'єктові

Можна присвоїти непримітивам властивість constructor.

const arr = [];
arr.constructor = String;
arr.constructor === String; // true
arr instanceof String; // false
arr instanceof Array; // true

const foo = new Foo();
foo.constructor = "bar";
foo.constructor === "bar"; // true

// тощо.

Це не замінить стару властивість constructor – вона спочатку була присутня в [[Prototype]] екземпляра, а не як власна властивість.

const arr = [];
Object.hasOwn(arr, "constructor"); // false
Object.hasOwn(Object.getPrototypeOf(arr), "constructor"); // true

arr.constructor = String;
Object.hasOwn(arr, "constructor"); // true: властивість примірника затіняє властивість прототипа

Але навіть коли Object.getPrototypeOf(a).constructor отримує нове значення, це не змінює решту поведінки об'єкта. Наприклад, поведінка instanceof контролюється Symbol.hasInstance, а не constructor:

const arr = [];
arr.constructor = String;
arr instanceof String; // false
arr instanceof Array; // true

Ніщо не захищає властивість constructor від присвоєння нового значення або затінення, тож використання її для з'ясування типу змінної зазвичай слід уникати на користь більш стійких способів, як то instanceof і Symbol.toStringTag для об'єктів або typeof для примітивів.

Зміна constructor прототипа функції-конструктора

Кожний конструктор має властивість prototype, яка стане [[Prototype]] примірника при виклику за участі оператора new. ConstructorFunction.prototype.constructor стане властивістю [[Prototype]] примірника, як показано вище.

Однак якщо ConstructorFunction.prototype присвоєно нове значення, то властивість constructor буде втрачена. Наприклад, наступний приклад показує поширений спосіб створення патерну успадкування:

function Parent() {
  // …
}
Parent.prototype.parentMethod = function () {};

function Child() {
  Parent.call(this); // Пересвідчитись, що все ініціалізовано як слід
}
// Спрямування [[Prototype]] об'єкта Child.prototype на Parent.prototype
Child.prototype = Object.create(Parent.prototype);

Значенням constructor примірників Child буде Parent, оскільки Child.prototype присвоєно нове значення.

Зазвичай це не є великою проблемою: мова майже ніколи не читає властивість constructor об'єкта. Єдиним винятком є використання @@species для створення нових екземплярів класу, але такі випадки рідкісні, та й усе одно слід використовувати запис extends для підкласів вбудованих класів.

Проте слідкувати, що Child.prototype.constructor завжди посилається на сам Child, важливо, коли якийсь викликач використовує constructor для доступу до початкового класу з екземпляра. Розгляньте наступний випадок: об'єкт має метод create(), щоб створювати себе.

function Parent() {
  // …
}
function CreatedConstructor() {
  Parent.call(this);
}

CreatedConstructor.prototype = Object.create(Parent.prototype);

CreatedConstructor.prototype.create = function () {
  return new this.constructor();
};

new CreatedConstructor().create().create(); // TypeError: new CreatedConstructor().create().create is undefined, since constructor === Parent

У прикладі вище викидається помилка, оскільки constructor зв'язано з Parent. Щоб цього уникнути, слід просто присвоїти необхідний конструктор, який ви збираєтеся використовувати.

function Parent() {
  // …
}
function CreatedConstructor() {
  // …
}

CreatedConstructor.prototype = Object.create(Parent.prototype, {
  // Повернути вихідний конструктор до Child
  constructor: {
    value: CreatedConstructor,
    enumerable: false, // Зробити його неітерованим, щоб він не з'являвся в циклах `for...in`
    writable: true,
    configurable: true,
  },
});

CreatedConstructor.prototype.create = function () {
  return new this.constructor();
};

new CreatedConstructor().create().create(); // доволі непогано

Зверніть увагу, що коли додати властивість constructor вручну, то критично необхідно зробити її неітерованою, щоб constructor не з'являвся у циклах for...in – як це зазвичай і буває.

Якщо код вище здається надто сильно схожим на шаблонний, то можна також розглянути використання Object.setPrototypeOf() для роботи з ланцюжком прототипів.

function Parent() {
  // …
}
function CreatedConstructor() {
  // …
}

Object.setPrototypeOf(CreatedConstructor.prototype, Parent.prototype);

CreatedConstructor.prototype.create = function () {
  return new this.constructor();
};

new CreatedConstructor().create().create(); // все одно працює, без відтворення властивості constructor

Статичний метод Object.setPrototypeOf() приносить власні потенційні недоліки щодо продуктивності, тому що всі раніше створені об'єкти, присутні в ланцюжку прототипів, повинні бути заново скомпільовані; однак якщо код ініціалізації вище виконується до того, як будуть створені Parent і CreatedConstructor, то цей ефект повинен бути мінімальним.

Розгляньмо іще один складний випадок.

function ParentWithStatic() {}

ParentWithStatic.startPosition = { x: 0, y: 0 }; // Статична властивість-член
ParentWithStatic.getStartPosition = function () {
  return this.startPosition;
};

function Child(x, y) {
  this.position = { x, y };
}

Child.prototype = Object.create(ParentWithStatic.prototype, {
  // Повернути вихідний конструктор до Child
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true,
  },
});

Child.prototype.getOffsetByInitialPosition = function () {
  const position = this.position;
  // Використання this.constructor, у надії на те, що getStartPosition існує як статичний метод
  const startPosition = this.constructor.getStartPosition();

  return {
    offsetX: startPosition.x - position.x,
    offsetY: startPosition.y - position.y,
  };
};

new Child(1, 1).getOffsetByInitialPosition();
// Error: this.constructor.getStartPosition is undefined, оскільки
// constructor – Child, що не має статичного метода getStartPosition

Щоб цей приклад працював як слід, можна скопіювати статичні властивості Parent до Child:

// …
Object.assign(Child, ParentWithStatic); // Зверніть увагу, що вони присвоюються до create() прототипа нижче
Child.prototype = Object.create(ParentWithStatic.prototype, {
  // Повернути вихідний конструктор до Child
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true,
  },
});
// …

Іще краще: можна змусити функції-конструктори самі розширяти одна одну, як це робить ключове слово extends для класів.

function ParentWithStatic() {}

ParentWithStatic.startPosition = { x: 0, y: 0 }; // Статична властивість-член
ParentWithStatic.getStartPosition = function () {
  return this.startPosition;
};

function Child(x, y) {
  this.position = { x, y };
}

// Коректне створення успадкування!
Object.setPrototypeOf(Child.prototype, ParentWithStatic.prototype);
Object.setPrototypeOf(Child, ParentWithStatic);

Child.prototype.getOffsetByInitialPosition = function () {
  const position = this.position;
  const startPosition = this.constructor.getStartPosition();

  return {
    offsetX: startPosition.x - position.x,
    offsetY: startPosition.y - position.y,
  };
};

console.log(new Child(1, 1).getOffsetByInitialPosition()); // { offsetX: -1, offsetY: -1 }

Знову таки, використання Object.setPrototypeOf() може мати негативний вплив на продуктивність, тому слід переконатися, що це відбувається безпосередньо після оголошення конструктора і до створення будь-яких примірників – щоб уникнути "ославлення" об'єктів.

Примітка: Ручне оновлення чи присвоєння constructor може призвести до різних, а іноді – дивних наслідків. Щоб цьому запобігти, слід визначити роль constructor у кожному окремому випадку. В більшості випадків constructor не використовується, і її повторне присвоєння не є необхідним.

Специфікації

Сумісність із браузерами

desktop mobile server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
constructor
Chrome Full support 1
Edge Full support 12
Firefox Full support 1
Internet Explorer Full support 8
Opera Full support 4
Safari Full support 1
WebView Android Full support 1
Chrome Android Full support 18
Firefox for Android Full support 4
Opera Android Full support 10.1
Safari on iOS Full support 1
Samsung Internet Full support 1.0
Deno Full support 1.0
Node.js Full support 0.10.0

Дивіться також