ECMA-262-5 in detail. Chapter 1. Properties and Property Descriptors.

Read this article in: Russian.

This chapter is devoted generally to one of new concepts of the ECMA-262-5 specification — to property attributes and mechanism of their handling — property descriptors.

Usually saying that “an object has some property” we mean an association between a property name and its value. But as we know from the ES3 analysis, a property structure is more complex than just a string name. It also has set of attributes — those which we already discussed in ES3, e.g. {ReadOnly}, {DontEnum} and other. So from this viewpoint a property is as an object itself.

For full understanding of this chapter I recommend reading the Chaper 7.2. OOP: ECMAScript implementation of the ECMA-262-3 series.

For working with properties and their attributes ES5 standardized several new API methods, which we’ll discuss shortly in detail:

// better prototypal inheritance
Object.create(parentProto, properties);

// getting the prototype
Object.getPrototypeOf(o);

// define properties with specific attributes
Object.defineProperty(o, propertyName, descriptor);
Object.defineProperties(o, properties);

// analyze properties
Object.getOwnPropertyDescriptor(o, propertyName);

// static (or "frozen") objects
Object.freeze(o);
Object.isFrozen(o);

// non-extensible objects
Object.preventExtensions(o);
Object.isExtensible(o);

// "sealed": non-extensible
// and non-configurable objects
Object.seal(o);
Object.isSealed(o);

// lists of properties
Object.keys(o);
Object.getOwnPropertyNames(o);

But let us give one after another.

In ES3 we had only the direct association between the property name and its value. Although, some implementation having their own extensions provide in ES3 concept of getters and setters, i.e. functions which are indirectly associated with property values. ECMA-262-5 standardizes this concept, and now in all we have three property types.

Also you should know that a property can be either own, i.e. contained directly by an object, or inherited, i.e. contained by one of the objects in the prototype chain.

There are named properties, which available for ECMAScript program and internal properties, which available directly only on implementation level (however, it is possible to manage some of them in ECMAScript program via special methods). We will consider them shortly.

Named properties are distinct by a set of attributes. Discussed in ES3 series, property attributes such as {ReadOnly}, {DontEnum} and other, in ES5 have been renamed to mean reversed boolean state. Thus, common for both (data and accessor) named property types in ECMA-262-5 are two attributes:

  • [[Enumerable]]
  • attribute (which stands for reversed state of {DontEnum} in ES3) determines in true state that a property is enumerated by a for-in enumeration.

  • [[Configurable]]
  • attribute (in ES3 — reversed state of {DontDelete}) in false state prevents attempts to delete the property, change the property to be an accessor property, or change its attributes (other than [[Value]]).

Notice, if [[Configurable]] attribute has been set once to false, it cannot be turned back to true. As we just said, we cannot change even other attributes, e.g. [[Enumerable]] in such case. We may though change [[Value]] attribute and [[Writable]], but only from true to false; not vice-versa — if [[Writable]] was already set to false, it cannot be turned to true in non-configurable property.

We will discuss other property attributes specific to the corresponding named property types shortly. Let’s consider property types in detail.

These properties which we already have and successfully use in ES3. Such property has a name (which is always a string) and a direct associated with it value.

For example:

// define in the declarative form
var foo = {
  bar: 10 // direct Number type value
};

// define in the imperative form,
// also direct, but Function type value, a "method"
foo.baz = function () {
  return this.bar;
};

The same as in ES3, in case if the value of a property is a function, such property is called a method. But this direct function values should not be confused with indirect special accessor-functions which will be considered below.

In addition to general attributes of named properties, a data property has the following attributes:

  • [[Value]]
  • attribute specifies a value retrieved by reading the property.

  • [[Writable]]
  • attribute (reversed state of {ReadOnly} in ES3) in false state prevents attempts to change the property’s [[Value]] attribute using [[Put]] internal method.

The complete attributes map for a named data property with default values is:

var defaultDataPropertyAttributes = {
  [[Value]]: undefined,
  [[Writable]]: false,
  [[Enumerable]]: false,
  [[Configurable]]: false
};

So, in default state properties are constants:

// define a global constant

Object.defineProperty(this, "MAX_SIZE", {
  value: 100
});

console.log(MAX_SIZE); // 100

MAX_SIZE = 200; // error in strict mode, [[Writable]] = false,
delete MAX_SIZE; // error in strict mode, [[Configurable]] = false

console.log(MAX_SIZE); // still 100

Unfortunately, in ES3 we had no control of property attributes what caused well known issues with augmentation of built-in prototypes. Because of dynamic mutable nature of the objects in ECMAScript, it is very convenient to plug-in the new functionality and use it, delegating to a prototype, as it would be “own” for an object. But without the control of e.g. {DontEnum} attribute in ES3, we all saw the problem with the for-in enumeration over the augmented in prototype arrays:

 // ES3

Array.prototype.sum = function () {
  // sum implementation
};

var a = [10, 20, 30];

// works fine
console.log(a.sum()); // 60

// but because of for-in examines the 
// prototype chain as well, the new "sum"
// property is also enumerated, because has
// {DontEnum} == false

// iterate over properties
for (var k in a) {
  console.log(k); // 0, 1, 2, sum
}

ES5 provides such control using special meta-methods for manipulating the object properties:

Object.defineProperty(Array.prototype, "sum", {

  value: function arraySum() {
    //  sum implementation
  },

  enumerable: false

});

// now with using the same example this "sum"
// is no longer enumerable

for (var k in a) {
  console.log(k); // 0, 1, 2
}

In the example above we specify the enumerable attribute manually and explicitly. However, as we mention, the default state for all attributes is false, so we could omit the explicit false setting.

And a simple assignment operator now corresponds to the reversed default state of all attributes (actually, what we have in ES3):

// simple assignment (if we create a new property)
foo.bar = 10;

// the same as
Object.defineProperty(foo, "bar", {
  value: 10,
  writable: true,
  enumerable: true,
  configurable: true
});

Notice also that meta-method Object.defineProperty is not only for creating object’s properties, but also for altering them. Moreover, it returns altered object, so we can use this method to bind newly created object to needed variable name, making it in one action:

// create "foo" object and define "bar" property
var foo = Object.defineProperty({}, "bar", {
  value: 10,
  enumerable: true
});

// alter value and enumerable attribute
Object.defineProperty(foo, "bar", {
  value: 20,
  enumerable: false
});

console.log(foo.bar); // 20

For getting array of own properties there are two meta-methods: Object.keys, which returns only enumerable properties, and Object.getOwnPropertyNames, which in turn returns both: enumerable and non-enumerable properties:

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"]

A named accessor property associates a name (also — only a string) with one or two accessor functions: a getter and a setter.

The accessor functions are used to store or retrieve a value that is associated with the property name indirectly.

As we have noticed, some implementations of ES3 already had this concept. But ES5 specifies this officially and provides slightly different syntax (from e.g. corresponding extension of SpiderMonkey) of such property kind definition.

In addition to general attributes, an accessor property has the following attributes which related with a getter and a setter respectively:

  • [[Get]]
  • attribute is a function object which is called every time for retrieving indirect value related with the property name. Do not confuse this property attribute with the same name internal method of an object — the general reader of a property value. So in case of accessor property, internal [[Get]] method of an object calls [[Get]] attribute of a property of the object.

  • [[Set]]
  • attribute being also a function is used in turn for setting the new value associated with a name. This attribute is called by the [[Put]] internal method of an object.

Notice, that the effect of the [[Set]] may, but is not required to, have an effect on the value returned by subsequent calls to the property’s [[Get]] internal method. In other words, if we set to a property e.g. a value 10, a getter then can return completely different value, e.g. 20, because the association is indirect.

And the complete attributes map for a named accessor property with default values is:

var defaultAccessorPropertyAttributes = {
  [[Get]]: undefined,
  [[Set]]: undefined,
  [[Enumerable]]: false,
  [[Configurable]]: false
};

Accordingly, if [[Set]] attribute is absent, an accessor property is read only — like in case of false [[Writable]] attribute of a data property.

An accessor property can be defined either using already mentioned above meta-method Object.defineProperty:

var foo = {};

Object.defineProperty(foo, "bar", {

  get: function getBar() {
    return 20;
  },

  set: function setBar(value) {
    // setting implementation
  }

});

foo.bar = 10; // calls foo.bar.[[Set]](10)

// independently always 20
console.log(foo.bar); // calls foo.bar.[[Get]]()

Or also in the declarative view using object initialiser:

var foo = {

  get bar () {
    return 20;
  },

  set bar (value) {
    console.log(value);
  }

};

foo.bar = 100;
console.log(foo.bar); // 20

Notice also one important feature related with configuration of an accessor property. As we have mentioned in [[Configurable]] attribute description, if it once has been set to false a property cannot be configured anymore (except the [[Value]] attribute of a data property). It can confuse e.g. in the following case:

// configurable false by default
var foo = Object.defineProperty({}, "bar", {
  get: function () {
    return "bar";
  }
});

// trying to reconfigure the "bar"
// property => exception is thrown
try {
  Object.defineProperty(foo, "bar", {
    get: function () {
      return "baz"
    }
  });
} catch (e) {
  if (e instanceof TypeError) {
    console.log(foo.bar); // still "bar"
  }
}

But exception won’t be thrown if reconfiguring value of the attribute in this case is the same. Although, it is not so useful (or even — useless) on practice, because we actually not reconfigure the getter and it still the same:

function getBar() {
  return "bar";
}

var foo = Object.defineProperty({}, "bar", {
  get: getBar
});

// no exception even if configurable is false,
// but practically such "re"-configuration is useless
Object.defineProperty(foo, "bar", {
  get: getBar
});

And as we have mentioned, the [[Value]] of a data property can be reconfigured even if [[Configurable]] is in false state; of course its [[Writable]] attribute should be set to true. Also being in true-state the [[Writable]] attribute can be set to false, but not vice-versa for non-configurable property:

var foo = Object.defineProperty({}, "bar", {
  value: "bar",
  writable: true,
  configurable: false
});

Object.defineProperty(foo, "bar", {
  value: "baz"
});

console.log(foo.bar); // "baz"

// change writable
Object.defineProperty(foo, "bar", {
  value: "qux",
  writable: false // changed from true to false, OK
});

console.log(foo.bar); // "qux"

// try to change writable again - back to true
Object.defineProperty(foo, "bar", {
  value: "qux",
  writable: true // ERROR
});

Also we can’t transform a property from the data to the accessor type and vice-versa if its [[Configuragle]] attribute if false. In true state of [[Configuragle]] attribute such transformation is possible; thus, the state of the [[Writable]] attribute is not important and can be false:

// writable false by default
var foo = Object.defineProperty({}, "bar", {
  value: "bar",
  configurable: true
});

Object.defineProperty(foo, "bar", {
  get: function () {
    return "baz";
  }
});

console.log(foo.bar); // OK, "baz"

Another obvious fact, that a property cannot be data and accessor types at the same time. That means that presence of some mutually exclusive attributes throws an exception:

// error, "get" and "writable" at the same time
var foo = Object.defineProperty({}, "bar", {
  get: function () {
    return "baz";
  },
  writable: true
});

// also error: mutually exclusive  "value" and "set" attributes
var baz = Object.defineProperty({}, "bar", {
  value: "baz",
  set: function (v) {}
})

Let’s recall also, that using setters and getters mostly makes more sense only when we need to encapsulate some complex calculations using auxiliary helper data, and making usage of this property convenient — i.e. just as it would be a simple data property. We already mentioned this in the section devoted encapsulation on the example with element.innerHTML property — where we abstractly say that “now html of this element is the following”, while inside the setter function for the innerHTML property there are difficult calculations and checks which cause then rebuilding of the DOM tree and updating the user interface.

For non-abstract things, using accessors may not be that useful. E.g.:

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

Not only we use accessors for non-abstract entity, but we also have created own “baz” property. In such cases a simple data property can be enough, which also may increase the performance.

The cases which really deserve using accessors usually are related with increasing of an abstraction, encapsulating auxiliary helper data. The simplest example:

var foo = {};

// encapsulated context
(function () {

  // some internal state
  var data = [];

  Object.defineProperty(foo, "bar", {

    get: function getBar() {
      return "We have " + data.length + " bars: " + data;
    },

    set: function setBar(value) {

      // call getter first
      console.log('Alert from "bar" setter: ' + this.bar);

      data = Array(value).join("bar-").concat("bar").split("-");

      // of course if needed we can update
      // also some public property
      this.baz = 'updated from "bar" setter: ' + value;

    },

    configurable: true,
    enumerable: true

  });

})();

foo.baz = 100;
console.log(foo.baz); // 100

// first getter will be called inside the setter:
// We have 0 bars:
foo.bar = 3;

// getting
console.log(foo.bar); // We have 3 bars: bar, bar, bar
console.log(foo.baz); // updated from "bar" setter: 3

Of course this example isn’t so practical, but it shows the main purpose of accessors — increasing of an abstraction encapsulating auxiliary internal data.

And another feature related with accessor properties is assignment to inherited accessor properties. As we know from ES3 series, inherited (data) properties are available for reading, but assignment (to a data property) always creates an own property:

Object.prototype.x = 10;

var foo = {};

// read inherited property
console.log(foo.x); // 10

// but with assignment
// create always own property
foo.x = 20;

// read own property
console.log(foo.x); // 20
console.log(foo.hasOwnProperty("x")); // true

In contrast with data properties, inherited accessor properties are available for modifications via assignment through an object which inherits these properties:

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; // set own property

console.log(proto.x); // 20

var a = Object.create(proto); // "a" inherits from "proto"

console.log(a.x); // 20, read inherited

a.x = 30; // set *inherited*, but not own

console.log(a.x); // 30
console.log(proto.x); // 30
console.log(a.hasOwnProperty("x")); //false

However, if we define a still inheriting from proto but with specifying x as own, assignment of course sets the own property:

var a = Object.create(proto, {
  x: {
    value: 100,
    writable: true
  }
});

console.log(a.x); // 100, read own

a.x = 30; // set also own

console.log(a.x); // 30
console.log(proto.x); // 20
console.log(a.hasOwnProperty("x")); // true

The same result obviously can be obtained and via setting an own property using meta-method, but not assignment operator:

var a = Object.create(proto);

a.x = 30; // set inherited

Object.defineProperty(a, "x", {
  value: 100,
  writable: true
});

a.x = 30; // set own

Another thing to note, is that if we try to shadow via assignment a non-writable inherited property, and if we are in strict mode, then TypeError exception is thrown. This is made regardless whether a property is data or an accessor. However, if we shadow the property not via assignment, but via Object.defineProperty, everything is fine:

"use strict";
 
var foo = Object.defineProperty({}, "x", {
  value: 10,
  writable: false
});
 
// "bar" inherits from "foo"
 
var bar = Object.create(foo);

console.log(bar.x); // 10, inherited
 
// try to shadow "x" property
// and get an error in strict
// mode, or just silent failure
// in non-strict ES5 or ES3
 
bar.x = 20; // TypeError

console.log(bar.x); // still 10, if non-strict mode
 
// however shadowing works
// if we use "Object.defineProperty"
 
Object.defineProperty(bar, "x", { // OK
  value: 20
});

console.log(bar.x); // and now 20

About strict mode read the next Chapter 2. Strict Mode of ES5 series.

The internal properties are not part of the ECMAScript language. They are defined by the specification purely for expository purposes. We already discussed them for ES3 specification.

ES5 in addition provides some new internal properties. You can find detailed description of all these properties in section 8.6.2. of the ECMA-262-5 specification. And because of we already discussed this concept in ES3 article, here we mention only some new internal properties.

For example, objects in ES5 can be sealed, frozen or just non-extensible, i.e. static. With all three states an internal [[Extensible]] property is related. It can be managed using special meta-methods:

var foo = {bar: 10};

console.log(Object.isExtensible(foo)); // true

Object.preventExtensions(foo);
console.log(Object.isExtensible(foo)); // false

foo.baz = 20; // error in "strict" mode
console.log(foo.baz); // undefined

Note, that [[Extensible]] internal property once setting to false cannot be turned back to true.

But even from non-extensible object some properties can be removed. To prevent this case a meta-method Object.seal can help which besides the [[Extensible]] internal property also set to false [[Configurable]] attribute of all properties of an object:

var foo = {bar: 10};

console.log(Object.isSealed(foo)); // false

Object.seal(foo);
console.log(Object.isSealed(foo)); // true

delete foo.bar; // error in strict mode
console.log(foo.bar); // 10

If we want to make an object completely static i.e. to freeze it, preventing also changing values of existing properties, then we can use corresponding Object.freeze meta-method. This method besides mentioned [[Configurable]] attribute and [[Extensible]] internal property, also for data properties sets attribute [[Writable]] to 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

Both sealed and frozen states cannot be turned off back.

The same as in ES3 we have ability to examine a [[Class]] internal property — still via the default value of Object.prototype.toString method:

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.
);

In contrast with ES3, ECMA-262-5 provides ability for reading the internal [[Prototype]] property — via the meta-method Object.getPrototypeOf. Also in the current version of the specification we can create object with specifying needed prototype object — using a meta-method Object.create:

// create "foo" object with two own
// properties "sum" and "length" and which has
// Array.prototype as its [[Prototype]] property

var foo = Object.create(Array.prototype, {
  sum: {
    value: function arraySum() {
      // sum implementation
    }
  },
  // non-enumerable but writable!
  // else array methods won't work
  length: {
    value: 0,
    enumerable: false,
    writable: true
  }
});

foo.push(1, 2, 3);

console.log(foo.length); // 3
console.log(foo.join("-")); "1-2-3"

// neither "sum", nor "length"
// are enumerable

for (var k in foo) {
  console.log(k); // 0, 1, 2
}

// getting prototype of "foo"
var fooPrototype = Object.getPrototypeOf(foo);

console.log(fooPrototype === Array.prototype); // true

But unfortunately, using even such approach we still cannot create an inherited from Array.prototype “class” with all functionality of normal arrays including overloaded [[DefineOwnProperty]] internal method (see 15.4.5.1) which handles e.g. length property. Having example above:

foo[5] = 10;
console.log(foo.length); // still 3

Still the only way to inherit from Array.prototype and at the same time to have all related overloaded internal methods, is to use normal array (i.e. an object which [[Class]] is "Array") and apply non-standard __proto__ property. So this is available not for all implementations:

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); // empty array

And unfortunately, in contrast with non-standard __proto__ extension of some ES3 implementations, ES5 does not provide ability for setting an object’s prototype.

As we saw ES5 allows control of the property attributes. The set of property’s attributes and their values is called in ES5 as a property descriptor.

Related with corresponding named property type, a descriptor can be either a data property descriptor or an accessor property descriptor.

Specification also defines concept of a generic property descriptor, i.e. a descriptor that is neither a data descriptor nor an accessor descriptor. And also a fully populated property descriptor that is either an accessor descriptor or a data descriptor and that has all of the fields that correspond to the property attributes. But that relates mostly to the implementation level.

Thus, because of specified default values for the attributes, if corresponding descriptor is empty, a data property is created. Obviously, a data property is also created if descriptor object contains either writable or value properties. In case if a descriptor object has either a get or a set property, accordingly an accessor property is defined. For getting the descriptor object of a property there is meta-method Object.getOwnPropertyDescriptor:

// define several properties at once
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
);

And Property Identifier type is used to associate a property name with its descriptor. So, properties being values of the Property Identifier type are pairs of the form (name, descriptor):

Abstractly:

foo.bar = 10;

// property is an object of
// the Property Identifier type

var barProperty = {
  name: "bar",
  descriptor: {
    value: 10,
    writable: true,
    enumerable: true,
    configurable: true
  }
};

In this first chapter we closely acquainted with one of the new concepts of the ECMA-262-5 specification. The next chapters will be dedicated to the new details of execution contexts such as Lexical Environments, Environment Records and other. As always, if you have questions or additions, we can discuss them in comments.


Written by: Dmitry A. Soshnikov.
Published on: 2010-04-28

Tags: , , , , , ,

 
 
 

18 Comments:

  1. Gravatar of Anton Anton
    29. April 2010 at 16:15

    Надеюсь это все-таки появится на русском, все-таки не так легко читается


  2. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    29. April 2010 at 16:32

    @Anton

    Посмотрим. Возможно позже, когда я допишу все части этой новой серии на английском; параллельно вряд ли сейчас буду писать.

    Но, этим может заняться кто-нибудь другой — естественно заинтересованный в JS и в этом цикле статей. Отсюда тогда будут ссылки. Постепенно, я смогу проверить качество и точность перевода в техническом плане.


  3. Gravatar of kangax kangax
    30. April 2010 at 08:07

    Nice overview :)

    Few things you might want to add:

    “all implementations creates a data property if corresponding descriptor is empty, and an accessor property if descriptor object has either a get or a set property”

    Of course data descriptor is created _not only_ when corresponding object is empty, but also when “writable” or “value” properties are present (you only mention empty and get/set).

    I find it easy to think about it in this way:

    1) If object is empty or value/writable properties are present, data descriptor is created

    2) If get/set properties are present, accessor descriptor is created

    Btw, does generic descriptor (which should be created if object is empty) serve any useful purpose?

    You can also mention that `TypeError` is supposed to be thrown when creating a property descriptor out of an object that has both “value”/”writable” and “get”/”set” properties (as per 8.10.5 — ToPropertyDescriptor).

    Another “gotcha” I didn’t see in the article is what happens when you try to overwrite accessor property. For example:

    var o = Object.defineProperty({ }, 'x', { 
      get: function(){ return 'bar' } 
    });
    
    Object.defineProperty(o, 'x', {
      get: function(){ return 'baz' }
    });
    
    o.x; // "bar"

    It might not be obvious, but property can only be overwritten when its [[Configurable]] is not `false`. Since in this example we do not set [[Configurable]] explicitly and its default value is `false`, the property can not be overwritten and an error is thrown. Defining it with [[Configurable]] == true obviously “solves” the problem:

    var o = Object.defineProperty({ }, 'x', { 
      get: function(){ return 'bar' },
      configurable: true
    });
    
    Object.defineProperty(o, 'x', {
      get: function(){ return 'baz' }
    });
    
    o.x; // "baz"

  4. Gravatar of Jens Jens
    30. April 2010 at 11:37

    Great article and… thank you!


  5. Gravatar of Dmitry A. Soshnikov Dmitry A. Soshnikov
    30. April 2010 at 14:56

    @Jens

    thanks.

    @kangax

    thanks for additions.

    Of course data descriptor is created _not only_ when corresponding object is empty, but also when “writable” or “value” properties are present (you only mention empty and get/set).

    Yes, of course, but this case seems obvious. The main goal is to specify what happens when we have an empty descriptor object. Although, I added this case too.

    You can also mention that `TypeError` is supposed to be thrown when creating a property descriptor out of an object that has both “value”/”writable” and “get”/”set” properties (as per 8.10.5 — ToPropertyDescriptor).

    Yes, added.

    It might not be obvious, but property can only be overwritten when its [[Configurable]] is not `false`. Since in this example we do not set [[Configurable]] explicitly and its default value is `false`, the property can not be overwritten and an error is thrown. Defining it with [[Configurable]] == true obviously “solves” the problem:

    Yup, also a good addition. Although, it is mentioned in the [[Configurable]] attribute definition, it is good to specify this case explicitly.

    By the way, the error is not thrown if reconfiguring attribute’s value (a getter in this case) is the same (because of SameValue check in 8.12.9). But practically such “re”-configuration seems useless.

    I mentioned all that cases, though.

    Btw, does generic descriptor (which should be created if object is empty) serve any useful purpose?

    No, don’t think so. At least — not for ECMAScript program. Of course, it serves a useful purpose on implementation level. See again “8.12.9 [[DefineOwnProperty]]“, where checks such as IsGenericDescriptor and other are everywhere. There also said, that default value for absent attributes is false, so for the ECMAScript program it doesn’t matter.

    Dmitry.


  6. Gravatar of LCamel LCamel
    25. January 2012 at 08:37

    “In addition to general attributes, an accessor property has the following properties which related with a getter and a setter respectively”

    should be

    “In addition to general attributes, an accessor property has the following *attributes* which related with a getter and a setter respectively”


  7. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    25. January 2012 at 08:43

    @LCamel, yes, “attributes” sounds better in this case (though, if to treat a property as an object, then then property may have properties, or, yes — attributes); fixed.


  8. Gravatar of Aleksey Aleksey
    21. February 2012 at 08:41

    Дмитрий, Ваши статьи как всегда на высоте! Спасибо!
    Хотел Вам сообщить о замеченной мной опечатке в тексте (хотя могу и ошибаться). Вы пишите

    For getting array of own properties there are two meta-methods: Object.keys, which returns only enumerable properties, and Object.getOwnPropertyNames, which in turn returns also non-enumerable properties:

    Далее идет пример. Мне кажется, что в последней строке примера

    alert(Object.getOwnPropertyNames(foo)); // ["bar", "baz", "x"]

    опечатка в том, что выведется только свойство, имеющее ключ x, так как остальные два добавлены в объект foo в стиле ES3, тем самым они по умолчанию являются enumerable.


  9. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    21. February 2012 at 09:56

    @Aleksey

    Спасибо, рад, что полезно.

    опечатка в том, что выведется только свойство, имеющее ключ x, так как остальные два добавлены в объект foo в стиле ES3, тем самым они по умолчанию являются enumerable.

    Нет, там все верно: Object.getOwnPropertyNames возвращает как enumerable, так и не-enumerable свойства.

    Скорей всего я использовал не совсем удачное предложение:

    Object.getOwnPropertyNames, which in turn returns also non-enumerable properties

    исправил на более явное.

    Во всех современных браузерах ES5 уже реализован в той или иной мере, поэтому эти примеры можно тестировать — например, в консоли Firebug. Хотя, я подумываю сделать эти примеры запускаемыми прямо из статьи.


  10. Gravatar of Aleksey Aleksey
    21. February 2012 at 21:57

    Дмитрий, я вчера тоже проверил в консоли google chrome – действительно, Object.getOwnPropertyNames возвращает и перечисляемые и неперечисляемые свойства объекта.
    Дмитрий, скажите, пожалуйста, Вы обладаете таким большим набором знаний, в частности по ECMAScript, из-за своего многолетнего огромного опыта и ранее полученных знаний, либо Вы очень хорошо изучаете соответствующую спецификацию?
    Просто столько, сколько я черпнул из Ваших статей, я не черпал еще нигде по ECMAScript. Заранее спасибо!


  11. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    21. February 2012 at 23:36

    @Aleksey

    Я думаю, тут комбинация ;) Спасибо еще раз.


  12. Gravatar of Alexander Petrov Alexander Petrov
    6. June 2012 at 00:13

    // try to change writable again - back to true
    Object.defineProperty(foo, "bar", {
      value: "qux",
      writable: false // ERROR
    });

    should be:

      writable: _true_ // ERROR

  13. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    6. June 2012 at 19:18

    @Alexander Petrov yep, thanks, fixed.


  14. Gravatar of Kim Kim
    6. September 2012 at 06:29

    Was there a reason for the implicit hoisting of k in

    for (var k in a) {

    ?
    Cheers.


  15. Gravatar of Kim Kim
    16. September 2012 at 00:27

    I notice you say “in default state properties are constants”.
    This is not totally correct.

    var obj1 = {};
    var obj1PropertyDesc;
    var obj2 = {};
    var obj2PropertyDesc;
     
    Object.defineProperty(obj1, 'propOnObj1', {
       value: 'value of propOnObj1',//,
       // writable: false,
       // enumerable: false,
       // configurable: false,
    });
     
    obj1PropertyDesc = Object.getOwnPropertyDescriptor(obj1, 'propOnObj1');
     
    // obj1PropertyDesc {
    //    configurable: false,
    //    enumerable: false,
    //    value: "value of propOnObj1",
    //    writable: false
    // }
     
    obj2.propOnObj2 = 'value of propOnObj2';
     
    obj2PropertyDesc = Object.getOwnPropertyDescriptor(obj2, 'propOnObj2');
     
    // obj2PropertyDesc {
    //    configurable: true,
    //    enumerable: true,
    //    value: "value of propOnObj2",
    //    writable: true
    // }

  16. Gravatar of Dmitry Soshnikov Dmitry Soshnikov
    16. September 2012 at 15:34

    @Kim

    Was there a reason for the implicit hoisting of k in

    for (var k in a) {

    ?

    No, there was no special reason. I guess it was just easier to have the the same semantics for var.

    I notice you say “in default state properties are constants”.
    This is not totally correct.

    Yes, I meant your first case, where propOnObj1 is the constant. Since the article is about new methods, the “default state” related exactly o definition of properties using those methods (in particular Object.defineProperty). Of course, the “default state” in assignment is non-constant (all attributes are true), and it goes from ES3.


  17. Gravatar of drifter drifter
    5. July 2013 at 16:44

    hi

    I am busy reading about something that just happened to me in Internet Explorer v9 (HTML Application *.hta) when I found your article.

    Maybe you want to test it and add it to your article, if I am indeed correct.

    The code looks like this:

    var foo={};
    Object.defineProperties(foo,{
        bar:{
            value:
                'barbarbar',
            writable:true,enumerable:true
        }
    });
    Object.seal(foo);
    
    var Foo=Object.create(foo);
    Object.defineProperties(Foo,{
        Bar:{
            value:
                'BarBarBar',
            writable:true,enumerable:true
        }
    });
    Object.seal(Foo);
    

    It would seem to me that Foo would have two properties:
    1) bar from the prototype of foo
    2) Bar from Foo itself

    This is in fact only the case when I remove the seal from Foo (last line of code).

    That means that when you seal an object it stops following the prototype chain correctly or this is its natural behavior. It just seems odd, since it works when the seal is removed and the seal should not hide prototype properties.

    thanks

    drifter


  18. Gravatar of mohit mohit
    23. August 2013 at 01:30

    With reference to — “It might not be obvious, but property can only be overwritten when its [[Configurable]] is not ‘false'”.
    Should it work for the data descriptors as well

    var foo = Object.defineProperty({},"x",{value : 10,configurable : false,writable:true})
    foo.x  //  10
    
    Object.defineProperty(foo,"x",{value : 20})
    foo.x // 20 even though configurable is false as writable is true
    	// TypeError Cannot define property x -- if writable is false

Leave a Reply

Code: For code you can use tags [js], [text], [ruby] and other.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>