Update 2013-08-25: Blog post “Protecting objects in JavaScript” ( Object.preventExtensions() , Object.seal() , Object.freeze() ).

Properties determine the state of an object in JavaScript. This blog post examines in detail how they work.

Kinds of properties

Named data properties (“properties”)

obj

"prop"

var obj = { prop: 123 };

console.log(obj.prop); // 123 console.log(obj["prop"]); // 123

obj.prop = "abc"; obj["prop"] = "abc";

Named accessor properties

var obj = { get prop() { return "Getter"; }, set prop(value) { console.log("Setter: "+value); } }

obj

> obj.prop 'Getter' > obj.prop = 123; Setter: 123

Internal properties

The internal property [[Prototype]] points to the prototype of an object. It can be read via Object.getPrototypeOf() . Its value can only be set by creating a new object that has a given prototype, e.g. via Object.create() or __proto__ [1].

. Its value can only be set by creating a new object that has a given prototype, e.g. via or [1]. The internal property [[Extensible]] determines whether or not one can add properties to an object. It can be read via Object.isExtensible() . It can be set false via Object.preventExtensions() . Once false , it cannot be become true again.

Property attributes

JavaScript has three different kinds of properties: named data properties, named accessor properties and internal properties.“Normal” properties of objects map string names to values. For example, the following objecthas a data property whose name is the stringand whose value is the number 123.You can get (read) a property:And you can set (write) a property:Alternatively, getting and setting a property value can be handled via functions. Those functions are called accessor functions. A function that handles getting is called a getter. A function that handles setting is called a setter.Let’s interact withSome properties are only used by the specification. They are called “internal”, because they are not directly accessible via the language, but they do influence its behavior. Internal properties have special names that are written in double square brackets. Two examples:All of the state of a property, both its data and its meta-data, is stored in. They are fields that a property has, much like an object has properties. Attribute keys are often written in double brackets.

The following attributes are specific to named data properties:

[[Value]] hold the property’s value, its data.

[[Writable]] holds a boolean indicating whether the value of a property can be changed.

[[Get]] holds the getter , a function that is called when a property is read. That function computes the result of the read access.

, a function that is called when a property is read. That function computes the result of the read access. [[Set]] holds the setter, a function that is called when a property is set to a value. The function receives that value as a parameter.

[[Enumerable]] holds a boolean. Making a property non-enumerable hides it from some operations (see below).

[[Configurable]] holds a boolean. If false, you cannot delete a property, change any of its attributes (except [[Value]]) or convert between data property and accessor property. In other words, [[Configurable]] controls the writability of a property’s meta-data.

Default values

The following attributes are specific to named accessor properties:All properties have the following attributes:If you don’t specify attributes, the following defaults are used:

Attribute key Default value [[Value]] undefined [[Get]] undefined [[Set]] undefined [[Writable]] false [[Enumerable]] false [[Configurable]] false

These defaults are especially important for property descriptors (see below).

Property descriptors

{ value: 123, writable: false, enumerable: true, configurable: false }

{ get: function () { return 123 }, enumerable: true, configurable: false }

Functions that use property descriptors

Object.defineProperty(obj, propName, propDesc)

Create or change a property on obj whose name is propName and whose attributes are specified via propDesc . Return the modified object. Example: var obj = Object.defineProperty({}, "foo", { value: 123, enumerable: true // writable and configurable via defaults });

Create or change a property on whose name is and whose attributes are specified via . Return the modified object. Example: Object.defineProperties(obj, propDescObj)

The batch version of Object.defineProperty() . Each property of propDescObj holds a property descriptor. The names of the properties and their values tell Object.defineProperties what properties to create or change on obj . Example: var obj = Object.defineProperties({}, { foo: { value: 123, enumerable: true }, bar: { value: "abc", enumerable: true } });

The batch version of . Each property of holds a property descriptor. The names of the properties and their values tell what properties to create or change on . Example: Object.create(proto, propDescObj?)

First, create an object whose prototype is proto . Then, if the optional parameter propDescObj has been specified, add properties to it – in the same manner as Object.defineProperties . Finally, return the result. For example, the following code snippet produces the same result as the previous snippet: var obj = Object.create(Object.prototype, { foo: { value: 123, enumerable: true }, bar: { value: "abc", enumerable: true } });

First, create an object whose prototype is . Then, if the optional parameter has been specified, add properties to it – in the same manner as . Finally, return the result. For example, the following code snippet produces the same result as the previous snippet: Object.getOwnPropertyDescriptor(obj, propName)

Returns the descriptor of the own (non-inherited) property of obj whose name is propName . If there is no such property, undefined is returned. > Object.getOwnPropertyDescriptor(Object.prototype, "toString") { value: [Function: toString], writable: true, enumerable: false, configurable: true } > Object.getOwnPropertyDescriptor({}, "toString") undefined

Enumerability

var proto = Object.defineProperties({}, { foo: { value: 1, enumerable: true }, bar: { value: 2, enumerable: false } }); var obj = Object.create(proto, { baz: { value: 1, enumerable: true }, qux: { value: 2, enumerable: false } });

proto

Object.prototype

> Object.getPrototypeOf({}) === Object.prototype true

Object.prototype

toString

hasOwnProperty

Operations affected by enumerability

Object.keys()

A property descriptor encodes the attributes of a property as an object. Each of the properties of that object corresponds to an attribute. For example, the following is the descriptor of a read-only property whose value is 123:You can achieve the same goal, immutability, via accessors. Then the descriptor looks as follows:The following functions allow you to work with property descriptors:This section explains which operations are influenced by enumerability and which aren’t. Below, we are assuming that the following definitions have been made:Note that objects (including) normally have at least the prototypeis where standard methods such asandare defined.Enumerability only affects two operations: The for-in loop and

The for-in loop iterates over the names of all enumerable properties, including inherited ones (note that none of the non-enumerable properties of Object.prototype show up):

> for (var x in obj) console.log(x); baz foo

Object.keys() returns the names of all own (non-inherited) enumerable properties:

> Object.keys(obj) [ 'baz' ]

Object.getOwnPropertyNames()

Operations that ignore enumerability

> "toString" in obj true > obj.toString [Function: toString]

> Object.getOwnPropertyNames(obj) [ 'baz', 'qux' ] > obj.hasOwnProperty("qux") true > obj.hasOwnProperty("toString") false > Object.getOwnPropertyDescriptor(obj, "qux") { value: 2, writable: false, enumerable: false, configurable: false } > Object.getOwnPropertyDescriptor(obj, "toString") undefined

obj.propName = value obj["propName"] = value delete obj.propName delete obj["propName"] Object.defineProperty(obj, propName, desc) Object.defineProperties(obj, descObj)

Best practices

> Object.keys([]) [] > Object.getOwnPropertyNames([]) [ 'length' ] > Object.keys(['a']) [ '0' ]

> Object.keys(Object.prototype) [] > Object.getOwnPropertyNames(Object.prototype) [ hasOwnProperty', 'valueOf', 'constructor', 'toLocaleString', 'isPrototypeOf', 'propertyIsEnumerable', 'toString' ]

If you want the names of all own properties, you need to use(see example below).All other operations ignore enumerability. Some read operations take inheritance into consideration:Other read operations only work with own properties:Creating, deleting and defining properties only affects the first object in a prototype chain:The general rule is that properties created by the system are non-enumerable, while properties created by users are enumerable:That especially holds for the methods in prototype objects:Thus, for your code, you should ignore enumerability. You normally shouldn’t add properties to built-in prototypes and objects, but if you do, you should make them non-enumerable to avoid breaking code.

As we have seen, non-enumerability mostly benefits for-in and ensures that legacy code using it won’t break. The non-enumerable properties create the illusion that for-in only iterates over the user-created own properties of an object. In your code, you should avoid for-in if you can [3].

If you use objects as maps from strings to values, you should only work with own properties and ignore enumerability. But there are more pitfalls for this use case [4].

Conclusion

In this post, we have examined the nature of properties, which is defined via so-called attributes. Note that actual JavaScript implementations do not necessarily organize properties via attributes, they are mainly an abstraction used by the ECMAScript specification. But one that is sometimes visible in the language itself, for example in property descriptors.

Further reading on 2ality:

Read “JavaScript properties: inheritance and enumerability” for more information on how inheritance and enumerability affect property-related operations.

Read “JavaScript inheritance by example” for an introduction to JavaScript inheritance.

References