6.7. Атрибуты свойств

We use cookies. Read the Privacy and Cookie Policy

Помимо имени и значения свойства обладают атрибутами, определяющими их доступность для записи, перечисления и настройки. В ECMAScript 3 не предусматривается возможность изменения атрибутов: все свойства, создаваемые программами, выполняющимися под управлением реализации ECMAScript 3, доступны для записи, перечисления и настройки, и нет никакой возможности изменить эти атрибуты. Данный раздел описывает прикладной интерфейс (API), определяемый стандартом ECMAScript 5 для получения и изменения атрибутов свойств. Данный API имеет особое значение для разработчиков библиотек, потому что он позволяет:

• добавлять методы в объекты-прототипы и делать их неперечислимыми, подобно встроенным методам;

• «ограничивать» возможности объектов за счет определения свойств, которые не могут изменяться или удаляться.

Для целей данного раздела мы будем рассматривать методы чтения и записи свойств с методами как атрибуты свойств. Следуя этой логике, можно даже сказать, что значение свойства с данными также является атрибутом. Таким образом, свойства имеют имя и четыре атрибута. Четырьмя атрибутами свойств с данными являются: значение (value), признак доступности для записи (writable), признак доступности для перечисления (enumerable) и признак доступности для настройки (configurable). В свойствах с методами доступа отсутствуют атрибуты value и writable: их доступность для записи определяется наличием или отсутствием метода записи. Поэтому четырьмя атрибутами свойств с методами доступа являются: метод чтения (get), метод записи (set), признак доступности для перечисления (enumerable) и признак доступности для настройки (configurable).

Методы получения и записи значений атрибутов свойств, предусмотренные стандартом ECMAScript 5, используют объект, называемый дескриптором свойства (property descriptor), представляющий множество из четырех атрибутов. Объект дескриптора свойства обладает свойствами, имена которых совпадают с именами атрибутов свойства, которое он описывает. То есть объекты-дескрипторы свойств с данными имеют свойства с именами value, writable, enumerable и configurable. А дескрипторы свойств с методами доступа вместо свойств value и writable имеют свойства get и set. Свойства writable, enumerable и configurable являются логическими значениями, а свойства get и set - функциями.

Получить дескриптор свойства требуемого объекта можно вызовом Object.get-OwnPropertyDescriptor():

// Вернет {value: 1, writable:true, enumerable:true, configurable:true}

Object.getOwnPropertyDescriptor({x:1}, "x");

// Теперь получим свойство octet объекта random, объявленного выше.

// Вернет { get: /*func*/. set:undefined, enumerable:true, configurable:true}

Object.getOwnPropertyDescriptor(random, "octet");

// Вернет undefined для унаследованных и несуществующих свойств.

Object.getOwnPropertyDescriptor({}, "х"); // undefined, нет такого свойства

Object.getOwnPropertyDescriptor({}, "toString"); // undefined, унаследованное

Как можно заключить из названия метода, Object.getOwnPropertyDescriptor() работает только с собственными свойствами. Чтобы получить атрибуты унаследованного свойства, необходимо явно выполнить обход цепочки прототипов (смотрите описание Object.getPrototypeOf() в разделе 6.8.1).

Чтобы изменить значение атрибута свойства или создать новое свойство с заданными значениями атрибутов, следует вызвать метод Object.defineProperty(), передав ему объект, в котором требуется выполнить изменения, имя создаваемого или изменяемого свойства и объект дескриптора свойства:

var о = {}; // Создать пустой объект без свойств

// Создать неперечислимое простое свойство х со значением 1.

Object.defineProperty(o, "х", { value : 1,

    writable: true, enumerable: false, configurable: true}):

// Убедиться, что свойство создано и является неперечислимым

о.х;           // => 1

Object.keys(o) // => []

// Теперь сделать свойство х доступным только для чтения

Object.defineProperty(o, "х", { writable: false });

// Попытаться изменить значение свойства

о.х = 2; // Неудача, в строгом режиме возбудит ТуреЕrror

Дескриптор свойства, передаваемый методу Object.defineProperty(), необязательно должен иметь все четыре атрибута. При создании нового свойства отсутствующие атрибуты получат значение false или undefined. При изменении существующего свойства для отсутствующих атрибутов будут сохранены текущие значения. Обратите внимание, что этот метод изменяет существующее собственное свойство или создает новое собственное свойство - он не изменяет унаследованные свойства.

Если возникнет необходимость создать или изменить сразу несколько свойств, можно воспользоваться методом Object.defineProperties(). Первым аргументом ему передается объект, который требуется изменить. Вторым аргументом - объект, отображающий имена создаваемых или модифицируемых свойств в дескрипторы этих свойств. Например:

var р = Object.defineProperties({},{

  х:{

    value: 1,

    writable: true,

    enumerable:true,

    configurable:true

  },

  y:{

    value: 1,

    writable: true,

    enumerable:true,

    configurable:true },

  r:{

    get: function() { return Math.sqrt(this.x*this.x + this.y*this.y) },

    enumerable:true,

    configurable:true

  }

});

В этом примере все начинается с пустого объекта, в который затем добавляются два свойства с данными и одно свойство с методами доступа, доступное только для чтения. Он опирается на тот факт, что Object.defineProperties() возвращает модифицированный объект (подобно методу Object.defineProperty()).

С методом Object.сreate(), определяемым стандартом ECMAScript 5, мы познакомились в разделе 6.1, где узнали, что первым аргументом этому методу передается объект, который будет служить прототипом для вновь созданного объекта. Этот метод также принимает второй необязательный аргумент, такой же, как и второй аргумент метода Object.defineProperties(). Если методу Object.create() передать множество дескрипторов свойств, они будут использованы для создания свойств нового объекта.

Методы Object.defineProperty() и Object.defineProperties() возбуждают исключение ТуреError, когда создание или изменение свойств запрещено. Например, при попытке добавить новое свойство в нерасширяемый объект (раздел 6.8.3). Другие причины, по которым эти методы могут возбудить исключение ТуреЕrror, имеют непосредственное отношение к атрибутам. Атрибут writable контролирует попытки изменить атрибут value. А атрибут configurable контролирует попытки изменить другие атрибуты (а также определяет возможность удаления свойства). Однако все не так просто. Например, значение свойства, доступного только для чтения, можно изменить, если это свойство доступно для настройки. Кроме того, свойство, доступное только для чтения, можно сделать доступным для записи, даже если это свойство недоступно для настройки. Ниже приводится полный перечень правил. Вызовы Object.defineProperty() или Object.defineProperties(), нарушающие их, возбуждают исключение ТуреЕrror:

• Если объект нерасширяемый, можно изменить существующие собственные свойства этого объекта, но нельзя добавить в него новые свойства.

• Если свойство недоступно для настройки, нельзя изменить его атрибуты configurable и enumerable.

• Если свойство с методами доступа недоступно для настройки, нельзя изменить его методы чтения и записи и нельзя превратить его в простое свойство с данными.

• Если свойство с данными недоступно для настройки, нельзя превратить его в свойство с методами доступа.

• Если свойство с данными недоступно для настройки, нельзя изменить значение его атрибута writable с false на true, но его можно Изменить с true на false.

• Если свойство с данными недоступно для настройки и для записи, нельзя изменить его значение. Однако изменить значение свойства, недоступного для записи можно, если оно доступно для настройки (потому что свойство можно сделать доступным для записи, изменить его значение и затем опять сделать свойство доступным только для чтения).

Пример 6.2 включает функцию extend(), которая копирует свойства из одного объекта в другой. Эта функция просто копирует имена и значения свойств и игнорирует их атрибуты. Кроме того, она не копирует методы чтения и записи из свойств с методами доступа, а просто преобразует их в свойства со статическими данными. В примере 6.3 показана новая версия extend(), которая копирует все атрибуты свойств с помощью Object.getOwnPropertyDescriptor() и Object.defineProperty(). Но на этот раз данная версия оформлена не как функция, а как новый метод объекта и добавляется в Object.prototype как свойство, недоступное для перечисления.

Пример 6.3. Копирование атрибутов свойств

/*

* Добавляет неперечислимый метод extend() в Object.prototype.

* Этот метод расширяет объекты возможностью копирования свойств из объекта,

* переданного в аргументе. Этот метод копирует не только значение свойств,

* но и все их атрибуты. Из объекта в аргументе копируются все собственные

* свойства (даже недоступные для перечисления), за исключением одноименных

* свойств, имеющихся в текущем объекте.

*/

Object.defineProperty(Object.prototype,

  "extend", // Определяется Object.prototype.extend

  {

    writable: true,

    enumerable: false, // Сделать неперечислимым

    configurable: true,

    value: function(o) { // Значением свойства является данная функция

    // Получить все собственные свойства, даже неперечислимые

    var names = Object.getOwnPropertyNames(o);

    // Обойти их в цикле

    for(var і = 0: і < names.length; i++) {

      // Пропустить свойства, уже имеющиеся в данном объекте

      if (names[i] in this) continue;

      // Получить дескриптор свойства из о

      var desc = Object.getOwnPropertyDescriptor(o,names[i]);

      // Создать с его помощью свойство в данном объекте

      Object.defineProperty(this, names[i], desc);

    }

  }

});