Properties determine the state of an object in JavaScript. This blog post examines in detail how they work.
var obj = {
prop: 123
};
You can get (read) a property:
console.log(obj.prop); // 123
console.log(obj["prop"]); // 123
And you can set (write) a property:
obj.prop = "abc";
obj["prop"] = "abc";
var obj = {
get prop() {
return "Getter";
},
set prop(value) {
console.log("Setter: "+value);
}
}
Let’s interact with obj:
> obj.prop
'Getter'
> obj.prop = 123;
Setter: 123
The following attributes are specific to named data properties:
| 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).
{
value: 123,
writable: false,
enumerable: true,
configurable: false
}
You can achieve the same goal, immutability, via accessors. Then the descriptor looks as follows:
{
get: function () { return 123 },
enumerable: true,
configurable: false
}
var obj = Object.defineProperty({}, "foo", {
value: 123,
enumerable: true
// writable and configurable via defaults
});
var obj = Object.defineProperties({}, {
foo: { value: 123, enumerable: true },
bar: { value: "abc", enumerable: true }
});
var obj = Object.create(Object.prototype, {
foo: { value: 123, enumerable: true },
bar: { value: "abc", enumerable: true }
});
> Object.getOwnPropertyDescriptor(Object.prototype, "toString")
{ value: [Function: toString],
writable: true,
enumerable: false,
configurable: true }
> Object.getOwnPropertyDescriptor({}, "toString")
undefined
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 }
});
Note that objects (including proto) normally have at least the prototype Object.prototype [2]:
> Object.getPrototypeOf({}) === Object.prototype
true
Object.prototype is where standard methods such as toString and hasOwnProperty are defined.
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' ]
If you want the names of all own properties, you need to use Object.getOwnPropertyNames() (see example below).
> "toString" in obj
true
> obj.toString
[Function: toString]
Other read operations only work with own properties:
> 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
Creating, deleting and defining properties only affects the first object in a prototype chain:
obj.propName = value
obj["propName"] = value
delete obj.propName
delete obj["propName"]
Object.defineProperty(obj, propName, desc)
Object.defineProperties(obj, descObj)
> Object.keys([])
[]
> Object.getOwnPropertyNames([])
[ 'length' ]
> Object.keys(['a'])
[ '0' ]
That especially holds for the methods in prototype objects:
> Object.keys(Object.prototype)
[]
> Object.getOwnPropertyNames(Object.prototype)
[ hasOwnProperty',
'valueOf',
'constructor',
'toLocaleString',
'isPrototypeOf',
'propertyIsEnumerable',
'toString' ]
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].
Further reading on 2ality: