Статичний метод Object.defineProperty()
визначає нову властивість безпосередньо на об'єкті, або модифікує існуючу властивість на об'єкті, та повертає об'єкт.
Заувага: Цей метод викликається прямо на конструкторі Object
, а не на екземплярі типу Object
.
The source for this interactive example is stored in a GitHub repository. If you'd like to contribute to the interactive examples project, please clone https://github.com/mdn/interactive-examples and send us a pull request.
Синтаксис
Object.defineProperty(obj, prop, descriptor)
Параметри
obj
- Об'єкт, на якому визначається властивість.
prop
- Ім'я або
символ
властивості для визначення чи модифікації. descriptor
- Дескриптор для властивості, що модифікується чи визначається.
Значення, що повертається
Об'єкт, що був переданий у функцію.
Опис
Цей метод дозволяє точно додавати чи модифікувати властивість об'єкту. Звичайне додавання властивості через призначення створює властивості, які з'являються під час переліку властивостей (цикл for...in
або метод Object.keys
), чиї значення можуть бути змінені та можуть бути видалені. Даний метод дозволяє змінити ці додаткові деталі у стані за замовчуванням. За замовчуванням, значення, додані за допомогою Object.defineProperty()
, є незмінними.
Дескриптори властивостей у об'єктах бувають двох основних видів: дескриптори даних та дескриптори доступу. Дескриптор даних - це властивість, що має значення, яке може бути або не бути доступне для запису. Дескриптор доступу - це властивість, описана парою функцій гетер-сетер. Дескриптор має бути одного з двох видів, і не може належати до обох.
І дескриптори даних, і дескриптори доступу є об'єктами. Вони мають наступні необов'язкові ключі (Значення за замовчуванням у випадку визначення властивостей через Object.defineProperty()):
configurable
- Дорівнює
true
тоді й тільки тоді, коли тип цього дескриптора властивості може бути змінений, та якщо властивість може бути видалена з відповідного об'єкту.
За замовчуваннямfalse
. enumerable
- Дорівнює
true
тоді й тільки тоді, коли ця властивість з'являється під час переліку властивостей на відповідному об'єкті..
За замовчуваннямfalse
.
Дескриптор даних також має наступні необов'язкові ключі:
value
- Значення, пов'язане з властивістю. Може бути будь-яким з чинних значень JavaScript (числом, об'єктом, функцією і т. д.).
За замовчуваннямundefined
. writable
- Дорівнює
true
тоді й тільки тоді, коли значення, пов'язане з властивістю, може бути змінене за допомогою оператора присвоєння.
За замовчуваннямfalse
.
Дескриптор доступу також має наступні необов'язкові ключі:
get
- Функція, що служить гетером властивості, або
undefined
, якщо гетера немає. Коли відбувається звернення до властивості, ця функція викликається без аргументів, аthis
присвоюється об'єкт, через який відбулось звернення до властивості (це може бути не той об'єкт, на якому властивість була визначена, через наслідування).
За замовчуваннямundefined
. set
- Функція, що служить сетером властивості, або
undefined
, якщо сетера немає. Коли властивості присвоюється значення, функція викликається з одним аргументом (значення, яке присвоюється), аthis
присвоюється об'єкт, через який відбувається присвоєння.
За замовчуваннямundefined
.
Якщо в дескриптора немає жодного з ключів value
, writable
, get
або set
, він сприймається як дескриптор даних. Якщо дескриптор має і value
або writable
, і get
або set
, викидається виняток.
Майте на увазі, що ці атрибути не обов'язково є особистими властивостями дескриптора. Успадковані властивості також будуть враховуватись. Щоб впевнитись, що значення за замовчуванням збережуться, ви можете заморозити Object.prototype
заздалегідь, явно вказуючи всі опції, або вказати null
через Object.create(null)
.
// використовуючи __proto__
var obj = {};
var descriptor = Object.create(null); // немає успадкованих властивостей
// за замовчуванням недоступний для запису, переліку та налаштування
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);
// явно задані ключі
Object.defineProperty(obj, 'key', {
enumerable: false,
configurable: false,
writable: false,
value: 'static'
});
// перероблення того самого об'єкта
function withValue(value) {
var d = withValue.d || (
withValue.d = {
enumerable: false,
writable: false,
configurable: false,
value: null
}
);
// уникаємо дублювання операції присвоєння значення
if (d.value !== value) d.value = value;
return d;
}
// ... і ...
Object.defineProperty(obj, 'key', withValue('static'));
// якщо замороження доступне, воно запобігає додаванню або
// видаленню властивостей прототипу об'єкта
// (value, get, set, enumerable, writable, configurable)
(Object.freeze || Object)(Object.prototype);
Приклади
Якщо хочете дізнатись, як використовувати метод Object.defineProperty
з двійковими прапорами, дивіться додаткові приклади.
Створення властивості
Коли зазначеної властивості в об'єкті не існує, Object.defineProperty()
створює нову властивість за описом. Поля можуть бути пропущені у дескрипторі, тоді для цих полів будуть задані значення за замовчуванням.
var o = {}; // Створює новий об'єкт
// Приклад властивості об'єкта, доданої
// за допомогою defineProperty з дескриптором даних
Object.defineProperty(o, 'a', {
value: 37,
writable: true,
enumerable: true,
configurable: true
});
// Властивість 'а' існує в об'єкті o, її значення дорівнює 37
// Приклад властивості об'єкта, доданої
// за допомогою defineProperty з дескриптором доступу
var bValue = 38;
Object.defineProperty(o, 'b', {
get: function() { return bValue; },
set: function(newValue) { bValue = newValue; },
enumerable: true,
configurable: true
});
o.b; // 38
// Властивість 'b' існує в об'єкті o, її значення дорівнює 38
// Значення o.b тепер завжди ідентичне bValue,
// якщо o.b не була перевизначена
// Не можна змішувати обидва типи
Object.defineProperty(o, 'conflict', {
value: 0x9f91102,
get: function() { return 0xdeadbeef; }
});
// викидає TypeError: value існує
// тільки в дескрипторах даних,
// get існує тільки в дескрипторах доступу
Модифікація властивості
Коли властивість вже існує, Object.defineProperty()
намагається модифікувати властивість згідно значень у дескрипторі та поточної конфігурації об'єкта. Якщо атрибут configurable
старого дескриптора дорівнював false
, властивість вважається недоступною для налаштування. Неможливо змінити жоден атрибут властивості-аксесора, недоступної для налаштування. Для властивості-значення можливо змінити значення, якщо властивість доступна для запису, також можливо змінити атрибут writable
з true
на false
. Неможливо переключатись між типами властивостей, коли властивість недоступна для налаштування.
Помилка TypeError
викидається при спробах змінити атрибути властивості, недоступної для налаштування (окрім value
та writable
, якщо їх дозволено змінювати), крім випадку, коли поточне та нове значення однакові.
Атрибут writable
Коли атрибут властивості writable
встановлений у false
, властивість вважається недоступною для запису. Їй не можна переприсвоїти значення.
var o = {}; // Створює новий об'єкт
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
console.log(o.a); // виводить 37
o.a = 25; // Помилка не викидається
// (Викидалась би у строгому режимі,
// навіть якщо значеня таке саме)
console.log(o.a); // виводить 37. Присвоєння не спрацювало.
// строгий режим
(function() {
'use strict';
var o = {};
Object.defineProperty(o, 'b', {
value: 2,
writable: false
});
o.b = 3; // викидає TypeError: "b" доступна лише для читання
return o.b; // без попереднього рядка вертає 2
}());
Як видно з прикладу, спроби запису у властивість, недоступну для запису, не змінюють її, але й не викидають помилки.
Атрибут enumerable
Атрибут властивості enumerable
визначає, чи буде властивість підхоплена методом Object.assign()
або оператором розкладу. Для не символьних
властивостей він також визначає, чи буде властивість з'являтись у циклі for...in
та у Object.keys()
, чи ні.
var o = {};
Object.defineProperty(o, 'a', {
value: 1,
enumerable: true
});
Object.defineProperty(o, 'b', {
value: 2,
enumerable: false
});
Object.defineProperty(o, 'c', {
value: 3
}); // enumerable за замовчуванням дорівнює false
o.d = 4; // enumerable за замовчуванням дорівнює true,
// якщо властивість створюється через присвоєння
Object.defineProperty(o, Symbol.for('e'), {
value: 5,
enumerable: true
});
Object.defineProperty(o, Symbol.for('f'), {
value: 6,
enumerable: false
});
for (var i in o) {
console.log(i);
}
// виводить 'a' та 'd' (у невизначеному порядку)
Object.keys(o); // ['a', 'd']
o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
o.propertyIsEnumerable('d'); // true
o.propertyIsEnumerable(Symbol.for('e')); // true
o.propertyIsEnumerable(Symbol.for('f')); // false
var p = { ...o }
p.a // 1
p.b // undefined
p.c // undefined
p.d // 4
p[Symbol.for('e')] // 5
p[Symbol.for('f')] // undefined
Атрибут configurable
Атрибут configurable
контролює одночасно, чи може властивість бути видалена з об'єкту, та чи можуть її атрибути (інші, крім value
та writable
) бути змінені.
var o = {};
Object.defineProperty(o, 'a', {
get() { return 1; },
configurable: false
});
Object.defineProperty(o, 'a', {
configurable: true
}); // викидає TypeError
Object.defineProperty(o, 'a', {
enumerable: true
}); // викидає TypeError
Object.defineProperty(o, 'a', {
set() {}
}); // викидає TypeError (set не був визначений попередньо)
Object.defineProperty(o, 'a', {
get() { return 1; }
}); // викидає TypeError
// (хоча новий get робить рівно те саме)
Object.defineProperty(o, 'a', {
value: 12
}); // викидає TypeError
// ('value' можна змінити, коли 'configurable' дорівнює false,
// але не у цьому випадку, через аксесор 'get')
console.log(o.a); // виводить 1
delete o.a; // Нічого не відбувається
console.log(o.a); // виводить 1
Якби атрибут configurable
властивості o.a
дорівнював true
, жодна з помилок не з'явилася б, і властивість була б видалена наприкінці.
Додавання властивостей та значень за замовчуванням
Важливо враховувати, яким чином задаються значення атрибутів за замовчуванням. Часто є різниця між простим присвоєнням значення через крапкову нотацію та використанням Object.defineProperty()
, як показано нижче у прикладі.
var o = {};
o.a = 1;
// є еквівалентом:
Object.defineProperty(o, 'a', {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
// З іншого боку,
Object.defineProperty(o, 'a', { value: 1 });
// є еквівалентом:
Object.defineProperty(o, 'a', {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
Користувацьки сетери та гетери
Приклад нижче показує, як реалізувати об'єкт, що самоархівується. Коли призначається властивість temperature
, масив archive
отримує запис журналу подій.
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
У цьому прикладі гетер завжди повертає одне й те саме значення.
var pattern = {
get() {
return 'Що б ти не призначив, ' +
'я завжди повертаю цей рядок';
},
set() {
this.myname = "Це рядок з моїм ім'ям";
}
};
function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', pattern);
}
var instance = new TestDefineSetAndGet();
instance.myproperty = 'тест';
console.log(instance.myproperty);
// Що б ти не призначив, я завжди повертаю цей рядок
console.log(instance.myname); // Це рядок з моїм ім'ям
Успадкування властивостей
Якщо властивість-аксесор була успадкована, її методи get
та set
викликатимуться при зверненні до властивості та будуть модифіковані на об'єктах-нащадках. Якщо ці методи використовують змінну для зберігання значення, це значення буде спільним для усіх об'єктів.
function myclass() {
}
var value;
Object.defineProperty(myclass.prototype, "x", {
get() {
return value;
},
set(x) {
value = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1
Це можна виправити, зберігаючи значення у іншій властивості. У методах get
та set
this
вказує на об'єкт, що використовується для звернення або для модифікації властивості.
function myclass() {
}
Object.defineProperty(myclass.prototype, "x", {
get() {
return this.stored_x;
},
set(x) {
this.stored_x = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined
На відміну від властивостей-аксесорів, властивості-значення завжди присвоюються самому об'єкту, а не прототипу. Проте, якщо успадковується властивість-значення, недоступна для запису, це все одно не дозволяє змінювати властивість на об'єкті.
function myclass() {
}
myclass.prototype.x = 1;
Object.defineProperty(myclass.prototype, "y", {
writable: false,
value: 1
});
var a = new myclass();
a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1
a.y = 2; // Ігнорується, викидає виняток у строгому режимі
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1
Специфікації
Специфікація | Статус | Коментар |
---|---|---|
ECMAScript 5.1 (ECMA-262) The definition of 'Object.defineProperty' in that specification. |
Standard | Початкове визначення. Реалізоване у JavaScript 1.8.5. |
ECMAScript 2015 (6th Edition, ECMA-262) The definition of 'Object.defineProperty' in that specification. |
Standard | |
ECMAScript (ECMA-262) The definition of 'Object.defineProperty' in that specification. |
Living Standard |
Сумісність з веб-переглядачами
BCD tables only load in the browser
The compatibility table on this page is generated from structured data. If you'd like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.
Примітки щодо сумісності
Перевизначення властивості length
об'єкта Array
Можливість перевизначити властивість масивів length
є предметом звичайних обмежень. (Властивість length
початково недоступна для налаштування та переліку, і доступна для запису. Таким чином, для незміненого масиву можливо змінити значення властивості length
або зробити її недоступною для запису. Неможливо змінити її доступність для переліку чи налаштування, або, якщо вона недоступна для запису, змінити її значення чи доступність для запису.) Однак, не усі переглядачі дозволяють це перевизначення.
Firefox від 4 до 22 викине TypeError
на всяку спробу будь-яким чином (дозволено це чи ні) перевизначити властивість масиву length
.
Версії Chrome, у яких реалізовано Object.defineProperty()
, у певних обставинах ігнорують значення довжини масиву, відмінне від поточного значення властивості масиву length
. У деяких обставинах зміна доступності для запису, схоже, непомітно не спрацьовує (і не викидає виняток). Разом з тим, деякі методи, що змінюють масив, такі як Array.prototype.push
, не визнають недоступну для запису довжину масиву.
Версії Safari, у яких реалізовано Object.defineProperty()
, ігнорують значення length
, яке відрізняється від поточної властивості масиву length
, і спроби змінити її доступність для запису не викидають помилки, але насправді не змінюють доступність властивості для запису.
Лише Internet Explorer 9 та новіші, а також Firefox 23 та новіші, схоже, повністю та коректно реалізують перевизначення властивості масивів length
. На сьогоднішній день не варто покладатись на те, що перевизначення властивості масиву length
працюватиме чи поводитиметься певним чином. І навіть коли ви можете на це покластись, існує дуже вагома причина не робити цього.
Примітки щодо Internet Explorer 8
Internet Explorer 8 реалізував метод Object.defineProperty()
, який може використовуватись тільки на об'єктах DOM. Кілька моментів варто зазначити:
- Спроби використати
Object.defineProperty()
на вбудованих об'єктах викине помилку. - Атрибутам властивості мають бути надані певні значення. Атрибути
configurable
,enumerable
таwritable
мають усі дорівнюватиtrue
для дескриптора даних, а такожtrue
дляconfigurable
,false
дляenumerable
для дескриптора доступу.(?) Будь-яка спроба задати інше значення (?) призведе до викидання помилки. - Переналаштування властивості вимагає спочатку видалення цієї властивості. Якщо властивість не була видалена, вона залишиться такою, як була до спроби переналаштування.