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
об'єкта. Єдиним винятком є використання [Symbol.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 | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
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 |