Object.defineProperty()

Статичний метод Object.defineProperty() визначає нову властивість безпосередньо на об'єкті, або модифікує існуючу властивість на об'єкті, та повертає об'єкт.

Заувага: Цей метод викликається прямо на конструкторі Object, а не на екземплярі типу Object.

Синтаксис

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 Latest Draft (ECMA-262)
The definition of 'Object.defineProperty' in that specification.
Draft

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
definePropertyChrome Full support 5Edge Full support 12Firefox Full support 4IE Full support 9
Notes
Full support 9
Notes
Notes Also supported in Internet Explorer 8, but only on DOM objects and with some non-standard behaviors.
Opera Full support 11.6Safari Full support 5.1
Notes
Full support 5.1
Notes
Notes Also supported in Safari 5, but not on DOM objects.
WebView Android Full support YesChrome Android Full support 18Firefox Android Full support 4Opera Android Full support 12Safari iOS Full support YesSamsung Internet Android Full support Yesnodejs Full support Yes

Legend

Full support  
Full support
See implementation notes.
See implementation notes.

Примітки щодо сумісності

Перевизначення властивості 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 для дескриптора доступу.(?) Будь-яка спроба задати інше значення (?) призведе до викидання помилки.
  • Переналаштування властивості вимагає спочатку видалення цієї властивості. Якщо властивість не була видалена, вона залишиться такою, як була до спроби переналаштування.

Див. також