Read this article in: English.
Введение
Эта глава посвященна одной из новых концепций в спецификации ECMA-262-5 — атрибутам свойств и механизму работы с ними, дескрипторам свойств.
Обычно, говоря, что объект имеет какие-то свойства, мы имеем ввиду связь между именем свойства и его значением. Но, как мы знаем из анализа ES3, структура свойства несколько сложнее, чем просто строковое имя. Оно также имеет набор атрибутов — те, которые мы уже обсудили в статьях об ES3, например {ReadOnly}
, {DontEnum}
и другие. С этой точки зрения свойство само по себе является объектом.
Для полного понимания этой главы рекомендуется прочитать главу 7.2. ООП: Реализация в ECMAScript из серии статей об ECMA-262-3.
Новые методы API
Для работы со свойствами и их атрибутами ES5 вводит несколько новых API методов, которые мы обсудим детально:
// улучшенное прототипное наследование Object.create(parentProto, properties); // получение прототипа Object.getPrototypeOf(o); // объявление свойств со специальными атрибутами Object.defineProperty(o, propertyName, descriptor); Object.defineProperties(o, properties); // анализ свойств Object.getOwnPropertyDescriptor(o, propertyName); // статические (или “замороженные”) объекты Object.freeze(o); Object.isFrozen(o); // нерасширяемые объекты Object.preventExtensions(o); Object.isExtensible(o); // “запечатанные”: нерасширяемые // и неконфигурируемые объекты Object.seal(o); Object.isSealed(o); // список свойств Object.keys(o); Object.getOwnPropertyNames(o);
Но давайте все по порядку.
Виды Свойств
В ES3 у нас была только прямая связь между именем свойства и его значением. Хотя, некоторые реализации имея свои собственные расширения, предоставляли концепцию геттеров и сеттеров в ES3. ECMA-262-5 стандартизирует эту концепцию, и сейчас мы имеем в своём распоряжении три вида свойств.
Также мы знаем, что свойство может быть либо собственным, т.е. находящимся непосредственно в объекте, так и унаследованным, т.е. находящимся в одном из объектов в цепи прототипов.
Существуют именованные свойства, которые доступны для ECMAScript программы и внутренние свойства, которые доступны только на уровне реализации (однако есть возможность управлять некоторыми из них в программе ECMAScript через специальные методы). Мы их вскоре обсудим.
Атрибуты свойств
Именованные свойства различаются набором атрибутов. Рассмотренные ранее в серии статей об ES3, такие атрибуты свойств как {ReadOnly}
, {DontEnum}
и другие, в ES5 были переименованы с обратным булевым состоянием. При этом, существуют два общих атрибута для обоих видов (данные и аксессоры) именованных свойств в ECMA-262-5:
[[Enumerable]]
атрибут (является обратным состоянием
{DontEnum}
в ES3) определяет в истинном состоянии, что свойство доступно для перечисления при помощиfor-in
цикла.[[Configurable]]
атрибут (в ES3 — обратное состояние
{DontDelete}
) в ложном состоянии делает невозможным удаление свойства, измение его типа или изменение его атрибутов (отличные от[[Value]]
).
Обратите внимание, что если атрибут [[Configurable]]
был определен как false
, он не может быть изменен обратно в true
. Как было уже сказано, мы не сможем даже изменить другие атрибуты, например [[Enumerable]]
. Однако, мы сможем изменить атрибут [[Value]]
и атрибут [[Writable]]
, но только с true
в false
(обратное не верно: если [[Writable]]
был уже определен как false
, он не может быть возвращен в true
в неконфигурируемом состоянии).
Мы рассмотрим остальные атрибуты свойств, специфичные для соответствующих видов свойств несколько позже, а пока, давайте разберем виды свойств.
Именованные свойства-данные
Это те своства, которые мы уже успешно использовали в ES3. Такие свойства имеют имя (которое всегда является строкой) и напрямую ассоциированное с этим именем значение.
Например:
// определяем в декларативной форме var foo = { bar: 10 // явное значение свойства типа Number }; // определяем в императивной форме, // также явное значение, однако тип уже Function и свойство является методом foo.baz = function () { return this.bar; };
Также как и в ES3, в случае, когда значением свойства является функция, такое свойство называется методом. Однако, эти явные функции не путать с аксессорами, которые будут рассмотрены ниже.
В дополнение к базовым атрибутам именованных свойств, свойство данных имеет следующие атрибуты:
[[Value]]
атрибут определяющий значение возвращаемое при чтении свойства.
[[Writable]]
атрибут (обратное состояние
{ReadOnly}
в ES3) в ложном состоянии делает невозможным изменение атрибута свойства[[Value]]
используя внутренний метод[[Put]]
.
Полная карта атрибутов для именованных свойств данных со значениями по умолчанию является следующей:
var defaultDataPropertyAttributes = { [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false };
Таким образом в изначальном состоянии свойства — это константы:
// определяем глобальную константу Object.defineProperty(this, "MAX_SIZE", { value: 100 }); console.log(MAX_SIZE); // 100 MAX_SIZE = 200; // ошибка в строгом режиме, [[Writable]] = false, delete MAX_SIZE; // ошибка в строгом режиме, [[Configurable]] = false console.log(MAX_SIZE); // все еще 100
К сожалению, в ES3 у нас не было какого-либо контроля над атрибутами свойств, что приводило к известной проблеме с изменением встроенных прототипов. Из-за динамической изменяющейся природы объектов в ECMAScript можно легко добавить новую функциональность и использовать делегирующие свойства так, как будто они являются “собственными” для объекта. Но без контроля, например над атрибутом {DontEnum}
в ES3 мы получали проблему, при которой это свойство появляется в циклах for-in
:
// ES3 Array.prototype.sum = function () { // реализация sum }; var a = [10, 20, 30]; // работает хорошо console.log(a.sum()); // 60 // но из-за того что for-in также проверяет // цепочку прототипов новое совойство “sum” // также будет перечислено, потому что // имеет {DontEnum} == false // проходим по свойствам for (var k in a) { console.log(k); // 0, 1, 2, sum }
ES5 предлагает такой контроль при помощи мета-методов для манипуляции свойствами объекта:
Object.defineProperty(Array.prototype, "sum", { value: function arraySum() { // реализация sum }, enumerable: false }); // сейчас свойство “sum” неперечислимо for (var k in a) { console.log(k); // 0, 1, 2 }
В примере выше мы определили атрибут enumerable
вручную и явно. Однако, как уже было упомянуто, значением по умолчание всех атрибутов является false
и мы можем опустить явное определение атрибутов как false
.
Простой же оператор присваивания теперь соотносится с инвертированным состоянием по-умолчанию всех атрибутов (то что мы имели в ES3):
// простое присваивание (если мы сосздали новое свойство) foo.bar = 10; // так же как и... Object.defineProperty(foo, "bar", { value: 10, writable: true, enumerable: true, configurable: true });
Заметьте также, что мета-метод Object.defineProperty
используется не только для создания свойств объекта, но также и для изменения его атрибутов. Более того, он возвращает измененный объект, и мы можем использовать этот метод для связки вновь созданного объекта с нужной переменной одним действием:
// создаем объект “foo” и определяем свойсво “bar” var foo = Object.defineProperty({}, "bar", { value: 10, enumerable: true }); // изменяем атрибуты значения и перечислимости Object.defineProperty(foo, "bar", { value: 20, enumerable: false }); console.log(foo.bar); // 20
Для получения собственных свойств объекта существует два мета-метода: Object.keys
, который возвращает только перечислимые свойства, и Object.getOwnPropertyNames
, который в свою очередь возвращает как перечислимые так и неперечислимые свойства:
var foo = {bar: 10, baz: 20}; Object.defineProperty(foo, "x", { value: 30, enumerable: false }); console.log(Object.keys(foo)); // ["bar", "baz"] console.log(Object.getOwnPropertyNames(foo)); // ["bar", "baz", "x"]
Именованные аксессор-свойства
Именованное аксессор-свойство ассоциирует строковое имя с одним или двумя аксессорными функициями: геттером и сеттером.
Аксессорные функции используются для косвенной записи и чтения значений, ассоциируемых со свойством.
Как мы отметили, некоторые реализации ES3 уже имели эту концепцию. Однако ES5 определяет её официально и предостваляет немного измененный синтаксис (отличный, например, от подобного расширения в SpiderMonkey).
В дополнение к базовым атрибутам, аксессорное свойство имеет следующие атрибуты:
[[Get]]
атрибут является функцией, которая вызывается каждый раз, как производится попытка чтения свойства. Не путайте этот атрибут свойства с внутренним методом объекта с таким же именем, базовым методом чтения значения свойства. Так в случае аксессорного свойства, внутренний метод объекта
[[Get]]
вызывает атрибут свойства объекта[[Get]]
.[[Set]]
атрибут также является функцией, которая испольуется для записи значения свойства. Этот атрибут вызывается внутренним методом объекта
[[Put]]
.
Обратите внимание, что эффект от вызова [[Set]]
может, но не обязательно произведет изменение значения возвращаемого последующим вызовом [[Get]]
. Иначе говоря, если мы сделаем set
свойству, например 10
, геттер может вернуть совершенно другое значение, например 20
, потому что связь тут неявная.
Полная карта атрибутов для именованного свойства аксессора со значениями по умолчанию является следующей:
var defaultAccessorPropertyAttributes = { [[Get]]: undefined, [[Set]]: undefined, [[Enumerable]]: false, [[Configurable]]: false };
Соответсвенно, если атрибут [[Set]]
отсутствует, свойство может быть только прочитано — как в случае с false
у атрибута [[Writable]]
свойства данных.
Аксессор-свойство может быть объявленно уже упомянутым выше мета-методом Object.defineProperty
:
var foo = {}; Object.defineProperty(foo, "bar", { get: function getBar() { return 20; }, set: function setBar(value) { // реализация сеттера } }); foo.bar = 10; // вызываем foo.bar.[[Set]](10) // независимо всегда 20 console.log(foo.bar); // вызываем foo.bar.[[Get]]()
Или же декларативно, используя инициализатор объекта:
var foo = { get bar () { return 20; }, set bar (value) { console.log(value); } }; foo.bar = 100; console.log(foo.bar); // 20
Заметьте также одно важную особенность касающуюся конфигурации аксессор-свойства. Как мы уже упоминали в описании атрибута [[Configurable]]
, если он был выставлен в false
, свойство не может быть более переконфигурировано (кроме атрибута [[Value]]
свойства данных). Это может сбить с толку, например в следующем случае:
// configurable false по умолчанию var foo = Object.defineProperty({}, "bar", { get: function () { return "bar"; } }); // пытаемся переконфигурировать свойство "bar" // получаем исключение try { Object.defineProperty(foo, "bar", { get: function () { return "baz" } }); } catch (e) { if (e instanceof TypeError) { console.log(foo.bar); // все еще "bar" } }
Но исключение не будет выброшено если преконфигурируемое значение атрибута останется прежним. На приктике, однако, это не особо является полезным, т.к. мы фактически не переконфигурируем геттер и он остается прежним:
function getBar() { return "bar"; } var foo = Object.defineProperty({}, "bar", { get: getBar }); // никакого исключения если configurable равен false // но на практике такое “переконфигурирование” бесполезно Object.defineProperty(foo, "bar", { get: getBar });
И как уже упоминалось, аттрибут [[Value]]
свойства данных может быть переконфигурирован даже, если [[Configurable]]
равен false
. Конечно атрибут [[Writable]]
должен быть при этом равен true
. Также атрибут [[Writable]]
в состоянии true
может быть выставлен в false
, но не наоборот для неконфигурируемых свойств:
var foo = Object.defineProperty({}, "bar", { value: "bar", writable: true, configurable: false }); Object.defineProperty(foo, "bar", { value: "baz" }); console.log(foo.bar); // "baz" // изменяем writable Object.defineProperty(foo, "bar", { value: "qux", writable: false // изменено с true в false }); console.log(foo.bar); // "qux" // пытаемся изменить writable еще раз обратно в true Object.defineProperty(foo, "bar", { value: "qux", writable: true // ОШИБКА });
Также мы не сможем изменить тип свойства из свойства данных в свойство аксессора и наоборот, если его атрибут [[Configurable]]
находится в false
. В истинном состоянии атрибута [[Configurable]]
такое изменение возможно. Таким образом, состояние атрибута [[Writable]]
не важно и может быть false
:
// writable ложно по умолчанию var foo = Object.defineProperty({}, "bar", { value: "bar", configurable: true }); Object.defineProperty(foo, "bar", { get: function () { return "baz"; } }); console.log(foo.bar); // OK, "baz"
Другой очевидный факт заключается в том, что свойство не может быть одновременно двух типов. Это означает, что присутствие каких либо специализированных атрибутов, не принадлежащих текущему типу свойства, вызывает исключение:
// ошибка, “get” и “writable” одновременно var foo = Object.defineProperty({}, "bar", { get: function () { return "baz"; }, writable: true }); // также ошибка из-за наличия специализированных атрибутов “value” и “set” var baz = Object.defineProperty({}, "bar", { value: "baz", set: function (v) {} })
Стоит также отметить, что использование геттера и сеттера оправданно и имеет смысл больше в случаях, когда нам нужно инкапсулировать некоторые сложные вычисления со вспомогательными данными и сделать использование данного свойства удобным — как если бы оно было обычным свойством данных. Мы уже отмечали этот нюанс в части посвященной инкапсуляции на примере свойства element.innerHTML
, где мы абстрактно говорим при присваивании, что “свойство html этого элемента сейчас следующее“, в то время как сеттер функция для свойства innerHTML
содержит в себе сложные вычисления и проверки, которые приводят к перестройке дерева объектов DOM и обновления пользовательского интерфейса.
Для сущностей, не требующих дополнительной абстракции, геттеры и сеттеры могут быть не столь полезны. Например:
var foo = {}; Object.defineProperty(foo, "bar", { get: function getBar() { return this.baz; }, set: function setBar(value) { this.baz = value; } }); foo.bar = 10; console.log(foo.bar); // 10 console.log(foo.baz); // 10
Мы не только используем аксессоры для неабстрактной сущности, но также создали собственное свойство объекта baz
. В данном случае можно использовать простое свойство данных, что улучшит производительность.
Случаими, оправдывающими использование аксессоров являются абстракция и инкапсуляция вспомогательных данных. Простой пример:
var foo = {}; // энкапсулированный контекст (function () { // какое-то внутреннее состояние var data = []; Object.defineProperty(foo, "bar", { get: function getBar() { return "We have " + data.length + " bars: " + data; }, set: function setBar(value) { // вызываем геттер вначале console.log('Alert from "bar" setter: ' + this.bar); data = Array(value).join("bar-").concat("bar").split("-"); // конечно, если требуется, мы можем обновить // also some public property некоторые внешние свойства this.baz = 'updated from "bar" setter: ' + value; }, configurable: true, enumerable: true }); })(); foo.baz = 100; console.log(foo.baz); // 100 // первый геттер будет вызван внутри сеттера // bar равен 3: foo.bar = 3; // проверяем console.log(foo.bar); // We have 3 bars: bar, bar, bar console.log(foo.baz); // updated from "bar" setter: 3
Данный пример не особо практичен, но он показывает главное назначение аксессоров — увеличение уровня абстракции и инкапсуляции внутренних данных.
Еще одной особенностью, связанной с аксессорами является запись в унаследованные аксессорные свойства. Как мы знаем из серии ES3, унаследованные свойства доступны для чтения, но не для записи: при присвоении обычным свойствам, всегда создается собственное свойство:
Object.prototype.x = 10; var foo = {}; // читаем унаследованное свойство console.log(foo.x); // 10 // но при присвоении // создается всегда собственное свойство объекта foo.x = 20; // читаем собственное свойство console.log(foo.x); // 20 console.log(foo.hasOwnProperty("x")); // true
В противоположность свойствам данных, унаследованные аксессоры доступны для модификации через присвоение свойству наследующего объекта:
var _x = 10; var proto = { get x() { return _x; }, set x(x) { _x = x; } }; console.log(proto.hasOwnProperty("x")); // true console.log(proto.x); // 10 proto.x = 20; // задаем собственное свойство console.log(proto.x); // 20 var a = Object.create(proto); // "a" наследуется от "proto" console.log(a.x); // 20, читаем унаследованное "x" a.x = 30; // задаем *унаследованное* свойство console.log(a.x); // 30 console.log(proto.x); // 30 console.log(a.hasOwnProperty("x")); //false
Однако, если мы создадим объект а
наследуемый от proto
со своим свойством x
, присвоение, конечно, повлияет на собственное свойство объекта:
var a = Object.create(proto, { x: { value: 100, writable: true } }); console.log(a.x); // 100, читаем собственное свойство a.x = 30; // задаем также собственное console.log(a.x); // 30 console.log(proto.x); // 20 console.log(a.hasOwnProperty("x")); // true
Тот же самый результат мы получим, если зададим собственное свойство используя мета-метод, а не оператор присваивания:
var a = Object.create(proto); a.x = 30; // меняем унаследованное свойство Object.defineProperty(a, "x", { value: 100, writable: true }); a.x = 30; // меняем собственное свойство
Также стоит упомянуть то, что, если мы попытаемся скрыть посредством присвоения незаписываемое унаследованное свойство в строгом режиме (strict mode), мы получим исключение TypeError
. Это произойдет независимо от того, является ли свойство аксессором или данным. Однако, если мы скроем свойство не через присвоение, а через Object.defineProperty
, исключения не будет:
"use strict"; var foo = Object.defineProperty({}, "x", { value: 10, writable: false }); // "bar" наследуется от "foo" var bar = Object.create(foo); console.log(bar.x); // 10, inherited 10, унаследовали // пытаемся скрыть свойство “х” // и получаем ошибку в строгом // режиме, или просто тихий сбой // исполнения в нестрогом ES5 или ES3 bar.x = 20; // TypeError console.log(bar.x); // все еще 10, если нестрогий режим // однако скрытие сработает если // использовать Object.defineProperty Object.defineProperty(bar, "x", { // OK value: 20 }); console.log(bar.x); // а сейчас 20
Про строгий режим читайте в следующей главе серии ES5.
Внутренние свойства
Внутренние свойства не являются частью языка ECMAScript. Они определены спецификаций больше для объяснения поведения внутренних операторов. Мы их уже рассматривали в отношении спецификации ES3.
ES5 предоставляет дополнительные внутренние свойства. Вы можете найти детальное описание всех этих свойств в секции 8.6.2 спецификации ECMA-262-5. Поскольку мы уже обсудили эту концепцию в статье посвященной ES3, здесь мы затроним только некоторые новые свойства.
Например, объект в ES5 может быть запечатан, заморожен или просто стать нерасширяемым, т.е. статическим. Со всеми этими состояниями связано внутреннее свойство [[Extensible]]
. Оно управляется с помощью специальных мета-метадов:
var foo = {bar: 10}; console.log(Object.isExtensible(foo)); // true Object.preventExtensions(foo); console.log(Object.isExtensible(foo)); // false foo.baz = 20; // ошибка в строгом режиме console.log(foo.baz); // undefined
Заметьте, что внутреннее свойство [[Extensible]]
однажды установленное в false
, не можеть быть возвращено в true
.
Но даже из такого нерасширяемого объекта некоторые свойства возможно удалить. Чтобы это предотвратить используется мета-метод Object.seal
, который кроме внутреннего свойства [[Extensible]]
также выставляет в false
атрибут [[Configurable]]
всех свойств объкта:
var foo = {bar: 10}; console.log(Object.isSealed(foo)); // false Object.seal(foo); console.log(Object.isSealed(foo)); // true delete foo.bar; // ошибка в строгом режиме console.log(foo.bar); // 10
Если мы хотим сделать объект полностью статичным, т.е. заморозить его, предотвратив тем самым изменение существующих свойств, мы можем использовать соответсвующий мета-метод Object.freeze
. Этот метод кроме уже упомянутых атрибута [[Configurable]]
и внутреннего свойства [[Extensible]]
выставляет атрибут [[Writable]]
в false
всех свойств объекта:
var foo = {bar: 10}; print(Object.isFrozen(foo)); // false Object.freeze(foo); print(Object.isFrozen(foo)); // true delete foo.bar; // error in strict mode foo.bar = 20; // error in strict print(foo.bar); // 10
Как запечатанное так и замороженное состояния необратимы.
Так же как и в ES3 у нас есть возможность исследовать внутреннее свойство [[Class]]
через стандартный метод Object.prototype.toString
:
var getClass = Object.prototype.toString; console.log( getClass.call(1), // [object Number] getClass.call({}), // [object Object] getClass.call([]), // [object Array] getClass.call(function () {}) // [object Function] // etc. );
ECMA-262-5 также предоставляет возможность прочитать внутреннее свойство [[Prototype]]
через мета-метод Object.getPrototypeOf
. Также в текущей версии спецификации мы можем создать объект на основе прототипа используя метод Object.create
:
// создаем объект “foo” с двумя собственными // свойствами “sum” и “length” которые в качестве // значения свойства [[Prototype]] имеют Array.prototype var foo = Object.create(Array.prototype, { sum: { value: function arraySum() { // реализация sum } }, // неперчеслимое но изменяемое! // иначе методы массива не будут работать length: { value: 0, enumerable: false, writable: true } }); foo.push(1, 2, 3); console.log(foo.length); // 3 console.log(foo.join("-")); "1-2-3" // ни “sum” ни “length” не перечислимы for (var k in foo) { console.log(k); // 0, 1, 2 } // получаем прототип “foo” var fooPrototype = Object.getPrototypeOf(foo); console.log(fooPrototype === Array.prototype); // true
Но к сожалению используя только такой подход мы все еще не можем наследовать от Array.prototype
со всей функциональностью обычного массива, включая перегруженные методы, обрабатывающие, например, свойство length
:
foo[5] = 10; console.log(foo.length); // still 3
По-прежнему единственным способом унаследовать от Array.prototype
, и в тоже время иметь все связанные с ним прегруженные внутренние методы, является использование обычного массива (т.е. объекта, [[Class]]
которого равен "Array"
) и нестандартного свойство __proto__
. Это доступно не всем реализациям:
var foo = []; foo.__proto__= {bar: 10}; foo.__proto__.__proto__= Array.prototype; console.log(foo instanceof Array); // true console.log(foo.bar); // 10 console.log(foo.length); // 0 foo.push(20); foo[3] = 30; console.log(foo.length); //4 console.log(foo); // 20,,,30 foo.length = 0; console.log(foo); // пустой массив
И все же, ES5 не предоставляет возможностей для прямого определения прототипа объекта, кроме как через нестандартное свойство __proto__
некоторых реализаций языка.
Дескриптор свойств и типы идентификаторов свойств
Как мы видели, ES5 позволяет контролировать атрибуты свойств. Набор атрибутов свойств и их значений называется в ES5 дескриптором свойств.
Связанный с соответствующим именованным свойством дескриптор может быть как дескриптором свойства данных так и дескриптором аксессор-свойства.
Спецификация также определяет концепт базового дескриптора свойства, т.е. такого дескриптора который не принадлежит ни к одному типу свойств. И также полностью заполненный дескриптор свойства, который либо дескриптор аксессора либо, дескриптор данных и содержит все поля, относящиеся к атрибутам свойства. Но это больше относится у уровню реализации.
Таким образом, из-за указанных значений атрибутов по умолчанию, если дескриптор пустой, будет создано свойство данных. Очевидно, что свойство данных также создается, если дескриптор содержит либо writable
либо value
поля. В случае если дескриптор имеет либо get
или set
поля, будет создано аксессор-свойство. Для получения дескриптора свойства объекта используется мета-метод Object.getOwnProperyDescriptor
:
// определяем несколько свойств сразу Object.defineProperties(foo, { bar: {}, // "empty" descriptor, пустой дескриптор baz: {get: function () {}} }); var barProperty = Object.getOwnPropertyDescriptor(foo, "bar"); var hasOwn = Object.prototype.hasOwnProperty; console.log( barProperty.value, // undefined hasOwn.call(barProperty, "value"), // true barProperty.get, // undefined hasOwn.call(barProperty, "get"), // false barProperty.set, // undefined hasOwn.call(barProperty, "set"), // false ); console.log(foo.bar); // undefined (correct), in Rhino 1.73 - null console.log(foo.nonExisting); // undefined and in Rhino too // in contrast "baz" property is an accessor property var bazProperty = Object.getOwnPropertyDescriptor(foo, "baz"); console.log( bazProperty.value, // undefined hasOwn.call(bazProperty, "value"), // false bazProperty.get, // function hasOwn.call(bazProperty, "get"), // true bazProperty.set, // undefined hasOwn.call(bazProperty, "set"), // false );
Тип Идентификатора Свойства
используется для связи имени свойства и его дескриптора. Поэтому свойства, будучи значениями типа Идентификатор Свойства
, представляют собой пару в виде (имя, дескриптор)
:
Абстрактно:
foo.bar = 10; // свойство это объект типа Идентификатор Свойства var barProperty = { name: "bar", descriptor: { value: 10, writable: true, enumerable: true, configurable: true } };
Заключение
В этой первой статье мы ближе познакомились с одной из концепций спецификации ECMA-262-5. Следующая глава будет посвящена новым деталям, касающимся контекстов исполнения, таким как Лексическое Окружение, Записей Окружений и другим. Как всегда, если у вас есть вопросы или дополнения мы будем рады обсудить их в блоке комментариев.
Дополнительная литература
- 4.3 Definitions;
- 8.6 The Object Type;
- 8.10 The Property Descriptor and Property Identifier Specification Types;
- 8.12 Algorithms for Object Internal Methods;
- 15.2.3 Properties of the Object Constructor.
Авторы перевода: Артём Гончаров и Дмитрий Сошников
Дата перевода: 29.05.2013
Автор оригинала: Дмитрий Сошников [англ., читать »]
Дата публикации: 28.04.2010
Здравствуйте Дмитрий – появился вопрос.
Выполнив код что выше, я начал дальше пробовать атрибуты свойств в действии и следующими были такие строки кода:
Почему подобное произошло – ведь мы записывали в
Array.prototype
свойcтвоsum
?@Viktor Soroka
Это произошло из-за особенности работы функции
console.log
. Поскольку Вы объявили свойство"sum"
какenumerable
, оно будет выводиться в циклеfor-in
. И именноfor-in
используется при выводе объекта и, получается, массива тоже, в отладчике Chrome. Аfor-in
учитывает цепь прототипов, и находит там это свойство.Чтобы подтвердить, можно вывести любой другой массив через
console.log
, и там тоже это свойство будет (обратите внимание на странный формат вывода – вроде массив, а вроде и объект):Вероятно, это сделано, чтобы предупредить разработчиков о потенциальной ошибке, но, возможно, это просто баг
console.log
(я рекомендую сообщить разработчикам).Object.keys
же возращает правильное значение:Спасибо Дмитрий за оперативный ответ.