Object definition vs assignment
This article was taken from 2ality with study purpose. Reading, writing and rewriting is the way I chose to learn things and to register for consult on the future.
All credits to Dr. Axel Rauschmayer
The object definition and assignment are different things. The differences are subtle, but they exists.
Object definition uses a function to define a new property and is more verbose:
Object.defineProperty(user, "name", descriptors);
The main benefit of this approach is that you can pass descriptors, which allows you to configure the property behaviour. It can be use for either creating a property or modifying it.
Object assignment is the most common:
user.name = "John";
The assignment idea is to change a value. If there is a setter for the property name
in user
object or in its prototypes, it will be invoked. Otherwise the assigment will end up creating a new property in case ist doesn't exist, with default attributes.
Understanding the property attributes
In JavaScript, there are three kinds of properties:
- Named accessor properties: They exists thanks to a getter or a setter.
- Named data properties: A property that has a value. Those are the most common properties. They can be a method.
- Internal properties: Used internally by JavaScript.
Property attributes
Every property has specific attributes (fields), and those fields will change the way a property works.
- All properties:
[[Enumerable]]
: If a property is enumerable, it allows some operations such asfor...in
andObject.keys()
.[[Configurable]]
: If a property is configurable, then all of its attributes can be changed. Otherwise, just the[[Value]]
attribute can be changed via a definition.
- Named data properties:
[[Value]]
: is the value of the property.[[Writable]]
: determines if the value can be changed.
- Names accessor properties:
[[Get]]
: holds a getter method.[[Set]]
: holds a setter method.
Property descriptors
They are a set of property attributes aforementioned that composes a descriptor object. Example:
{
value: 123,
writable: false
}
They are used by methods, such as Object.defineProperty
, Object.getOwnPropertyDescriptor
and Object.create
.
In case there are properties missing from a descriptor, the default values takes place:
Property | Default Value |
---|---|
value | undefined |
get | undefined |
set | undefined |
writable | false |
enumerable | false |
configurable | false |
Internal properties
Among others, there let's observe these internal properties:
[[Prototype]]
: The prototype of the object.[[Extensible]]
: If the object is extensible - if new new properties can be added to it[[DefineOwnProperty]]
: Method for defining a property.[[Put]]
: Method for assigning a property.
What actually happens in definition and assignment
Defining a property is handled by the internal method [[DefineOwnProperty]](P, Desc, Throw)
. This internal function is called when Object.defineProperty
or Object.defineProperties
are called.
P
is the name of a property. Throw
specifies how the operation should reject a change: If Throw
is true
, then an exception is thrown, otherwise, the operation is silently aborted. When [[DefineOwnProperty]]
is called:
- If the object is not extensible, reject.
- Otherwise, if the property doesn't exist, creates a new one.
- In case the property exists and the object is not configurable it will be reject in case another property other than
[[Value]]
is changed. - Otherwise, the existing property is configurable and can be changed.
Assigning to a property is handled by the internal method [[Put]](P, Value, Throw)
. It is called on assignments such as:
user.name = "John";
P
and Throw
work the same way as on [[DefineOwnProperty]]
. When [[Put]]
is called, the following happens:
- If
P
is read-only somewhere in the prototype chain, operation is rejected. - If there is setter called
P
somewhere in the prototype chain, it will be called. - If there is no property with name
P
and the object is not extensible, then reject. - Otherwise, a new property is created with the same previous mechanism from
Object.defineProperty
, with descriptors:
this[[DefineOwnProperty]](
P,
{
value: Value,
writable: true,
enumerable: true,
configurable: true,
},
Throw
);
- In case a property exists and it's writable, that is invoked:
this[[DefineOwnProperty]](
P,
{
value: Value,
},
Throw
);
Examples
Check some examples that explain how definition and assignment works.
Read-only properties does not prevent definition
"use strict";
const obj = {};
Object.defineProperty(obj, "foo", {
value: "a",
writable: false, // read-only
configurable: true,
});
Assignment: results in exception:
obj.foo = 'b';
TypeError: obj.foo is read-only
Defiunition: creates a new own property:
Object.defineProperty(obj, "foo", {
value: "b",
});
obj.foo; // 'b'
Assignment operator does not change properties in prototypes
Since prototype is shared by all descendants, when one decides to change a property, a new local property is created and the prototype is not affected.
let proto = { foo: "a" };
let obj = Object.create(proto);
obj.foo = "b";
obj.foo; // 'b'
proto.foo; // 'a'
Only definition allows you to customize the property attributes
Since assignment has default attribute values, if you want to create specific attributes, you must use definition.
The properties of a literal object are added via definition
This statement:
let obj = {
foo: "a",
};
Is internally translated for:
let obj = new Object();
obj.foo = "a";
Which is the same of:
let obj = new Object();
Object.defineProperties(obj, {
foo: {
value: "a",
enumerable: true,
configurable: true,
writable: true,
},
});
Conclusion
Assignment will very likely be the better choice for most of the cases. I personally, can't remember when I needed to use definition. Assignment is easier to write/read and also more performatic, since definition is slow. However is good to know the difference and the nuances for them. In case you need to have more control over a property, for example, assignment can not be enough and definition the proper choice.