JavaScript Objects - Property Attributes
Learning outcomes:
- What are property attributes and descriptor objects
- Data properties and accessor properties
- The
Object.defineProperty()
method - Attributes of data properties —
[[Value]]
and[[Writable]]
- Attributes of accessor properties —
[[Get]]
and[[Set]]
- Enumerability and the
[[Enumerable]]
attribute - Configurability and the
[[Configurable]]
attribute - How to remember all this
Introduction
In ECMAScript 3 and earlier, there was not even a single way to tap into the internal attributes of object's property to configure the action made when the property gets accessed, i.e. retrieved or set.
Since this was a useful feature, it eventually got introduced in edition 5 of the ECMAScript spec, released in December 2009. In addition to this feature, ES5 also changed the names of a couple of internal attributes related to properties. In this chapter, we shall see all of this in extreme detail.
So shall we begin?
What are property attributes?
As we saw in the previous chapter, an object is an unordered collection of properties. If we zoom into the term 'properties' in this definition, we see that it is quite diverse in its own meaning.
Recall that we said that a property is merely a characteristic, a trait, of an object. That is indeed true. The properties of all objects in JavaScript do describe some idea, some characteristic of the underlying object.
However, every property in JavaScript is associated with certain things that define characteristics of the property itself. We refer to these things as internal attributes of the property.
For instance, some properties can't be modified in JavaScript. In the spec, this characteristic of such a property is defined by the internal [[Writable]]
attribute. If it's false
, the property couldn't be modified. We'll explore [[Writable]]
shortly below.
Now for each property, there are essentially a handful of these attributes. To encapsulate all these attributes for a given property, we have what's called a property descriptor.
As the name suggests,
A property descriptor contains 'things' that tell us whether the property holds some data directly or are there some functions to access it; does it show in a for...in
loop; can its value be changed; is it allowed to be configured; and so on and so forth.
As mentioned above, all these 'things' are given in the form of internal attributes.
Note that a property descriptor essentially describes the key of a property, NOT the corresponding value. For instance, if the descriptor says that the property's value couldn't be changed, this merely means that the key couldn't be assigned a different value; NOT that the actual value couldn't be changed/mutated.
According to the notational convention used by the spec, all internal attributes are represented by a pair of double square brackets ([[ ]]
). These internal attributes are only for explanatory purposes i.e. to describe the internal behavior of objects, explain algorithms for a given utility in the spec, etc.
Implementations of ECMAScript such as V8, SpiderMonkey, may use any way to model these internal attributes — they don't have to use (and actually none of them use) the [[
and ]]
naming convention.
[[
or ]]
in the name of an identifier would anyways lead to an error in the programming language in which the engine of JavaScript is written — usually C++. So the idea of actually using [[
or ]]
becomes pointless in the actual implementation anyway.Coming back to property descriptors, there are essentially two kinds of descriptors:
- Data property descriptors
- Accessor property descriptors
To understand these, we first need to understand the two kinds of properties in JavaScript: data properties and accessor properties.
Data and accessor properties
Most of the object properties that we have been working with so far in this course were data properties.
When the property is read, the corresponding value is returned. Similarly, when a value is set on the property, the value is assigned as it is to the property — there is just nothing in between the retrieval or between the assignment.
It's called a 'data' property because it directly contains the underlying data.
Data properties have two internal attributes that classify them as a data property: [[Value]]
and [[Writable]]
.
[[Value]]
— simply contains the value associated with the given property.[[Writable]]
— a Boolean that specifies whether it's allowed to assign a new value to the property or not, which is commonly known as 'writing to the property'.
The second kind of property in JavaScript is called on an accessor property.
When the property is read, a function is called the getter is invoked. Similarly when the property is written to, the setter function is invoked.
The name 'accessor' refers to the fact that the property accesses a piece of data via a function — it doesn't directly contain the data (as does a data property).
Accessor properties define at least one accessor function by virtue of the following attributes:
[[Get]]
— specifies a zero-arg function, known as the getter, to be called when the property is read.[[Set]]
— specifies a one-arg function, known as the setter, to be called when the property is assigned a value.
So to reiterate it, data properties defines the attributes [[Value]]
and [[Writable]]
while accessor properties define the attributes [[Get]]
and [[Set]]
.
A data property can't have a [[Set]]
or [[Get]]
attribute likewise an accessor property can't have a [[Value]]
or [[Writable]]
attribute.
Now apart from these two sets of attributes, a property could have two other attributes as described below:
[[Enumerable]]
— a Boolean specifying whether the property shows up in afor..in
loop.[[Configurable]]
— a Boolean specifying whether the property's attributes could be changed or the property be deleted via thedelete
keyword.
With all this brief information about property attributes in mind, it's time to take each section and explore it in even more detail.
First, let's talk about the new method Object.defineProperty()
intercalated in ECMAScript 5 to allow developers to configure property attributes of custom objects and, in some cases, of predefined objects as well.
The Object.defineProperty()
method
Introduced in ECMAScript 5, the Object.defineProperty()
method allows us to create a property on an object with given internal attributes.
The syntax of the method is quite intuitive:
Object.defineProperty(object, propertyName, propertyDescriptor)
The first object
argument specifies the object on which to define the property, the second propertyName
argument specifies the name of this property as a string, and finally the third argument specifies the property's descriptor object.
Quite easy to pick up, isn't this?
First the object, then the property's name, then the descriptor — that's it!
The propertyDescriptor
object could have the following properties:
value
— specifes a value for the[[Value]]
internal attribute.writable
— specifes a Boolean for the[[Writable]]
internal attribute.get
— specifes a zero-arg function for the[[Get]]
internal attribute.set
— specifes a one-arg function for the[[Set]]
internal attribute.enumerable
— specifes a Boolean for the[[Enumerable]]
internal attribute.configurable
— specifes a Boolean for the[[Configurable]]
internal attribute.
Specifying a property other than the ones shown above on the property descriptor object would simply be ignored.
Note that if the get
and/or set
properties are mentioned, then it's an error to set writable
or value
on the descriptor object.
As you can see, the nomenclature of all the properties shown above is synonymous to the nomenclature of the internal attributes with mainly one difference — the first letter of these properties is in lowercase.
Attributes of data properties
As we saw above, there are two attributes specific to a data property: [[Value]]
and [[Writable]]
. These correspond with the properties value
and writable
of the descriptor object passed to Object.defineProperty()
.
Let's consider a handful of examples to illustrate these attributes.
Below we create a simple object obj
with two properties a
and b
on it just as we are used to from the previous chapters.
var obj = { a: 'old' };
obj.b = 'old';
Properties created in these normal ways are all data properties and have their [[Writable]]
attribute set to true
by default, and [[Value]]
set to the given value ('old'
in the case above).
[[Writable]]
being true
is the reason why an expression like obj.a = 'new'
has an actual consequence.
Let's go on, assign a new value to obj.a
and obj.b
and see whether the value both these properties are actually changed:
var obj = { a: 'old' };
obj.b = 'old';
console.log(obj.a, obj.b);
obj.a = 'new';
obj.b = 'new';
console.log(obj.a, obj.b);
new new
As can be seen, it actually does change.
Now, let's go on and define a read-only property c
on the same obj
object:
var obj = { a: 'old' };
obj.b = 'old';
Object.defineProperty(obj, 'c', {
value: 'old',
writable: false
});
console.log(obj.c);
In line 4, we call Object.defineProperty()
to define a property c
on obj
with its value set to 'old'
and its [[Writable]]
attribute set to false
via the writable
property of the given descriptor object. This should have the effect of any write operation made to this property to simply fail.
Let's try writing a new value to this property.
var obj = { a: 'old' };
obj.b = 'old';
Object.defineProperty(obj, 'c', {
value: 'old',
writable: false
});
console.log(obj.c);
obj.c = 'new';
console.log(obj.c);
old
As is evident by the console's output, the value of obj.a
is 'old'
even after the assignment in line 4. This confirms the fact that setting a property on an object via Object.defineProperty()
with writable
set to false
effectively prevents overwriting the defined value of that property.
Let's also try redefining the property via the same Object.defineProperty()
method and see whether it works or not:
var obj = { a: 'old' };
obj.b = 'old';
Object.defineProperty(obj, 'c', {
value: 'old',
writable: false
});
console.log(obj.c);
// let's try to redefine obj.c
Object.defineProperty(obj, 'c', {
value: 'new',
writable: false
});
console.log(obj.c);
Clearly no! We can't even redefine the property.
Great!
Actually the error thrown above is because of the internal [[Configurable]]
attribute set to false
, not because of [[Writable]]
set to false
. We'll see this distinction in the section on [[Configurable]]
below.
Anyways, coming back to data properties, omitting value
in a data property descriptor results in [[Value]]
being set to undefined
(since [[Value]]
merely retrieves propertyDescriptor.value
, which would resolve to undefined
when value
is absent).
This can be seen as follows:
var obj = { a: 'old' };
obj.b = 'old';
Object.defineProperty(obj, 'c', {
// value: 'old',
writable: false
});
console.log(obj.c);
console.log('c' in obj);
true
The first log confirms the fact that the property c
has the value undefined
. The second log, however, tells us that the object obj
does contain the property x
, it's just that its value is undefined
.
Attributes of accessor properties
Accessor properties use accessor functions to retrieve and set a given piece of data. One is called a getter while the other is called the setter.
For a property to be termed an accessor property, it must have at least one of these accessor functions. The getter goes in the property's [[Get]]
attribute whereas the setter goes in the property's [[Set]]
attribute.
The corresponding properties of the description object passed to Object.defineProperty()
for [[Get]]
and [[Set]]
are get
and set
, respectively.
Let's create an accessor property.
Below, we have our old point
object from the previous chapter. We'll create an accessor property distanceFromOrigin
on it to get the distance of the point from the origin:
var point = { x: 0, y: 0 };
Object.defineProperty(point, 'distanceFromOrigin', {
get: function() {
return (this.x ** 2 + this.y ** 2) ** 0.5;
}
});
Now, let's retrieve this property:
var point = { x: 0, y: 0 };
Object.defineProperty(point, 'distanceFromOrigin', {
get: function() {
return (this.x ** 2 + this.y ** 2) ** 0.5;
}
});
point.x = 4;
point.y = 3;
console.log(point.distanceFromOrigin);
Voila!
We get the distance returned as if it was actually stored in distanceFromOrigin
, however it's computed on the fly.
Notice that there is no need to put a pair of parentheses after the property's read expression point.distanceFromOrigin
. In fact, doing so would be an error. We'll see why shortly below.
Moving on, if you have a good understanding of graphs and geometry, you'll know that it's completely impossible to determine the co-ordinates of a point, given its distance from the origin — there are just an infinite amount of possibilities!
Hence, in the case above, it seems that manually setting distanceFromOrigin
is senseless and should, likewise, throw an error to alert the developer of the erroneous set operation.
But how do we accomplish this?
Well, simply by setting up a setter function.
Consider the code below:
var point = { x: 0, y: 0 };
Object.defineProperty(point, 'distanceFromOrigin', {
get: function() {
return (this.x ** 2 + this.y ** 2) ** 0.5;
},
set: function(value) {
throw new Error("Can't set distanceFromOrigin!");
}
});
In addition to the getter, now we even define a setter. The function simply throws an error saying that distanceFromOrigin
can't be set, when we have a set operation for the property!
Let's try it out:
var point = { x: 0, y: 0 };
Object.defineProperty(point, 'distanceFromOrigin', {
get: function() {
return (this.x ** 2 + this.y ** 2) ** 0.5;
},
set: function(value) {
throw new Error("Can't set distanceFromOrigin!");
}
});
point.distanceFromOrigin = 100;
Perfect!
One thing to keep in mind is that the setter function takes in one arg when called. This is the value assigned to the property in the property's assignment expression.
Let's consider an example using a more meaningful setter function.
Suppose that we are coding an online shopping store in JavaScript. Each item is represented by an object with numerous properties on it. For now, to keep things simple, let's just focus on the two properties sellingPrice
and discountPrice
.
sellingPrice
specifies the price at which the item was originally being sold in the shopping store while discountPrice
specifies the price after applying a discount on the product.
Now, while developing this application, let's say that we feel that a discount
property would be required as well to specify the discount offered on the item, as a percentage value.
Obviously creating a data property would be redundant because, given the sellingPrice
and the discountPrice
, the discount can be easily determined by a bit of elementary math applied over these two values.
Hence, we choose to go with an accessor property discount
for every item.
Item()
constructor. We'll explore all these useful ideas in the next chapters.Below we have modeled one item of the shopping store using an object literal:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Now, let's define the discount
accessor property for this item:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Object.defineProperty(item, 'discount', {
get: function() {
return Math.round(
(this.sellingPrice - this.discountPrice)
/ this.sellingPrice * 100
);
}
});
console.log(item.discount + '% discount offered.');
If you think about it, it's possible to determine the value of discountPrice
when a value is assigned to discount
, given that we assert that sellingPrice
remains is fixed.
For instance, if initially the sellingPrice
is 100
and the discountPrice
is 80
, then discountPrice
would become 50
(and sellingPrice
would remain 100
).
sellingPrice
is fixed, because if this was not the case, there would have been one more possibility i.e. sellingPrice
becomes 160
while discountPrice
remains 80
.Likewise, let's also define a setter for discount
:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Object.defineProperty(item, 'discount', {
get: function() {
return Math.round(
(this.sellingPrice - this.discountPrice)
/ this.sellingPrice * 100
);
},
set: function(value) {
this.discountPrice = this.sellingPrice
- (this.sellingPrice * value / 100);
}
});
It's all just a bit of math in action, that's it!
With this accessor property set, why not go ahead and test it:
item.discount = 20
item.discount
Everything works perfectly. Superb!
As stated before, for any property to be considered an accessor property, it must have at least one of the accessor functions, i.e. a getter or a setter, defined.
If only one is specified, the other is set as undefined
, and henceforth, operations that involve this undefined
value would either return undefined
(in the case of an undefined
getter) or be ignored (in the case of an undefined
setter).
What's important to note is that in either way, there is NO error thrown, which ideally should've been the .
Enumerability
Regardless of the kind of property being defined, two attributes can always be set for that property. They are [[Enumerable]]
and [[Configurable]]
.
In this section, our aim is to shed some light over the former i.e the [[Enumerable]]
attribute. Let's start.
As said before, the [[Enumerable]]
attribute specifies whether a property is enumerable. But what exactly does it mean for a property to be enumerable?
Well:
for...in
loop.And that's just it.
If a property is enumerable, it will show up in for...in
, and if it ain't enumerable, it won't show up in for...in
.
Simple!
The enumerable
property of the descriptor object passed to Object.defineProperty()
specifies a value for the [[Enumerable]]
attribute. If false
, the corresponding property being set of the object is non-enumerable, and if true
, the property is enumerable.
Let's consider an example.
Following we have our same old code from the section above.
var point = { x: 0, y: 0 };
Object.defineProperty(point, 'distanceFromOrigin', {
get: function() {
return (this.x ** 2 + this.y ** 2) ** 0.5;
},
set: function(value) {
throw new Error("Can't set distanceFromOrigin!");
}
});
Since enumerable
isn't specified in the descriptor above, it's taken as false
. Hence, if we use point
with a for...in
loop, the property distanceFromOrigin
won't show up.
The code below demonstrates this:
/* ... */
for (var prop in point) {
console.log(prop);
}
y
If we want this property to show up in for...in
then we ought to set enumerable
to true
when defining distanceFromOrigin
.
This is exactly what we do below:
var point = { x: 0, y: 0 };
Object.defineProperty(point, 'distanceFromOrigin', {
get: function() {
return (this.x ** 2 + this.y ** 2) ** 0.5;
},
set: function(value) {
throw new Error("Can't set distanceFromOrigin!");
},
enumerable: true
});
Let's now rerun the for...in
loop over this new object.
/* ... */
for (var prop in point) {
console.log(prop);
}
y
distanceFromOrigin
As expected, now the property shows up in the loop.
Perfect!
Configurability
The last attribute left to be discovered in this chapter is [[Configurable]]
. It flags a property as configurable. And what does this mean?
Well,
For instance, if a property x
on an object o
is configurable, then its [[Enumerable]]
attribute could be changed from true
to false
, and vice versa, or it could be deleted via the expression delete o.x
.
As with all other attributes, the corresponding property on the descriptor object for the [[Configurable]]
attribute is configurable
.
If true
, the underlying property is configurable and if false
, the underlying property is non-configurable. Quite rudimentary, isn't it?
Moving on, contrary to what one might expect, going the other way round, a non-configurable property does not imply that none of its attributes could be changed — there might be a few of them that could be changed.
For instance, if a property x
on an object o
is non-configurable but is writable, then it's possible for its [[Value]]
to be changed to any other value. This is a fairly sensible decision — the property is writable and therefore should be able to register new value assignments.
Following the norm, it's example time.
Consider the code below:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Object.defineProperty(item, 'id', {
value: 1,
configurable: false
});
We define a property id
on the object item
with its value set to 1
, and configurable
set to false
. Since writable
isn't specified in the descriptor object, it's assumed to be false
.
Hence, a write operation on item.id
would have no effect, as shown below:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Object.defineProperty(item, 'id', {
value: 1,
configurable: false
});
item.id = 'new';
console.log(item.id);
Even, redefining the property via a second call to Object.defineProperty()
would fail; in fact, it would throw an error:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Object.defineProperty(item, 'id', {
value: 1,
configurable: false
});
Object.defineProperty(item, 'id', {
value: 'new',
configurable: false
});
console.log(item.id);
Now, as we stated above, this error is not caused because of [[Writable]]
set to false
, but rather because of both[[Writable]]
set to false
and[[Configurable]]
set to false
.
If we set configurable
to true
on the property descriptor object passed to the first call of Object.defineProperty()
above while keeping writable
as false
, it would be possible to assign a new value to id
via a second call to Object.defineProperty()
.
Consider the following code. It's the same as the second code snippet (shown above) in this section with the exception of the configurable
property — it's now set to true
:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Object.defineProperty(item, 'id', {
value: 1,
configurable: true
});
item.id = 'new';
console.log(item.id);
In line 11, we aim to write a new value to the property id
and then log it, in line 13. As expected, since the [[Writable]]
attribute of id
is false
(because writable
is omitted from the descriptor object above) it's not possible to change id
via a property assignment expression.
So with [[Configurable]]
set to true
, and [[Writable]]
still false
, a property can't be written to by means of a normal assignment expression.
Let's try the second option — redefining id
via a second call to Object.defineProperty()
.
Consider the code below. Here we redefine id
by calling Object.defineProperty()
a second time, and then log item.id
to see whether it's changed:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Object.defineProperty(item, 'id', {
value: 1,
configurable: true
});
Object.defineProperty(item, 'id', {
value: 'new',
configurable: true
});
console.log(item.id);
Quite surprisingly, as can be seen in the console, the property has changed!
The [[Writable]]
attribute of id
is still false
, nonetheless it didn't prevent id
from being assigned a new value via another call to Object.defineProperty()
above.
How is this possible?
Well, it's a sensible decision made by the standardising committee of ECMAScript to allow redefining a non-writable, configurable property via another call to Object.defineProperty()
.
Let's see how...
In the code above, configurable: true
means that the value of any attribute of id
could be changed to some other value. Assuming that redefining a non-writable configurable property was considered an error (which currently isn't, as we've seen above) any developer could've done the following to by-pass it:
var item = {
sellingPrice: 100,
discountPrice: 60
};
Object.defineProperty(item, 'id', {
value: 1,
configurable: true
});
// the value of id isn't changed here, likewise nothing new is written
// so no error is thrown
// however, [[Writable]] is set to true,
// allowing for subsequent write operations
Object.defineProperty(item, 'id', {
value: 1,
writable: true,
configurable: true
});
// a new value is written to id, since [[Writable]] is true
// likewise no error is thrown
// now the property is made non-writable again
Object.defineProperty(item, 'id', {
value: 'new',
configurable: true
});
Watch closely what's happening here — the comments are there to help understand what each call to Object.defineProperty()
is doing.
Let's see it all more closely:
- In the first call to
Object.defineProperty()
(in line 6), we set a propertyid
onitem
with the value1
,[[Writable]]
set tofalse
(since it's omitted) and[[Configurable]]
set totrue
as well (due to theconfigurable: true
property). - This would have meant that writing a new value to
id
, by a simple assignment expression such asitem.id = 2
, or by another call toObject.defineProperty()
, would cause an error due to[[Writable]]
beingfalse
. - But at the same time, this would have also meant that changing the value of any other attribute such as
[[Enumerable]]
or[[Writable]]
is allowed, or even to convert the property into an accessor property. - So, what's done to by-pass this is that first the property
id
is redefined usingObject.defineProperty()
(in line 15), keeping[[Value]]
the same but changingwritable
totrue
. - If the
[[Value]]
ofid
was changed in this call toObject.defineProperty()
an error would have been thrown, since[[Writable]]
wasfalse
initially onid
's descriptor. - Anyways, after the second call to
Object.defineProperty()
is complete,id
becomes writable. - Following this,
Object.defineProperty()
is called once more (in line 24) to change the[[Value]]
ofid
. - All other attributes of
id
are reset to their original values that they had after the first call toObject.defineProperty()
(in line 6). - In the code above, this is simply done by merely copy pasting the first call to
Object.defineProperty()
from line 6 to line 24, and adding the new value next to thevalue
property i.e. the string'new'
. - Once this third call to
Object.defineProperty()
reaches completion,id
is non-writable and configurable — just as it was after its first definition (in line 6) — however, now it has a new value.
Isn't this quite clever?
Well, it sure is.
And the ECMAScript standardising committee foresaw this kind of a hack and therefore allowed writing to a non-writable configurable property.
However, if the property is a non-writable non-configurable property, then there is just no chance of being able to use anything to change its value.
Let's see all the things brought about with a false
[[Configurable]]
attribute:
- The value of the
[[Enumerable]]
attribute can't be changed. - The value of the
[[Configurable]]
attribute can't be changed totrue
. - The underlying property can't be deleted via the
delete
keyword. - The underlying property can't be converted from a data property to an accessor property or from an accessor property to a data property.
Shown below are all these things:
Firstly, the value of the [[Enumerable]]
attribute can't be changed:
var obj = {};
Object.defineProperty(obj, 'a', {});
Object.defineProperty(obj, 'a', {
enumerable: true
});
When defining id
for the first time, its value is set to undefined
while its [[Writable]]
, [[Enumerable]]
and [[Configurable]]
attributes are all set to false
, as they are all omitted.
This means that the property is non-configurable, and therefore, changing the [[Enumerable]]
attribute would throw an error, which is exactly what happens in the code above.
Moving on, secondly, the value of the [[Configurable]]
attribute can't be changed to true
for a non-configurable property:
var obj = {};
Object.defineProperty(obj, 'a', {});
Object.defineProperty(obj, 'a', {
configurable: true
});
Thirdly, a non-configurable property can't be deleted:
var obj = {};
Object.defineProperty(obj, 'a', {});
delete obj.a;
console.log('a' in obj);
After deleting the property in line 4, when we check whether there exists a property 'a'
on obj
using the in
operator, we get back true
returned, which confirms that the property is still there and that the deletion didn't go successful.
In strict mode, the deletion throws an error:
'use strict';
var obj = {};
Object.defineProperty(obj, 'a', {});
delete obj.a;
Moreover, a non-configurable data property can't be transformed into an accessor property, and similarly a non-configurable accessor property can't be transformed into a data property:
var obj = {};
// obj.a is a data property
Object.defineProperty(obj, 'a', {});
// make it an accessor property
Object.defineProperty(obj, 'a', {
get: function() {}
});
Here, we demonstrate the transformation of a non-configurable data property to an accessor property; you could try the same for a non-configurable accessor property.
One last thing left to be discussed in regards to non-configurable data properties is detailed below.
Non-configurable data properties and writability
If a data property is non-configurable, i.e. has its [[Configurable]]
attribute set to false
, being able to change its [[Value]]
and [[Writable]]
attributes depends on the value of [[Writable]]
.
If the property is writable, i.e. its [[Writable]]
attribute is true
, this simply means that the property could be assigned a new value.
Hence, sensibly, the non-configurable nature of the property together with its writable nature does not disallow redefining it via another call to Object.defineProperty()
and then changing the value of the [[Value]]
attribute (while keeping all other attributes set to their original values).
In other words, it's possible to change the value of a non-configurable property, i.e. change the value of its [[Value]]
attribute, given that its [[Writable]]
attribute is true
.
In addition to this, we could even change the [[Writable]]
attribute of a non-configurable property from true
to false
, but NOT the other way round.
This is allowed since it makes the property even more strict in its operations — previously the property could be assigned a new value but now it couldn't.
Only those changes are prevented that make a property less strict in its operation. For instance, it's not possible to change the [[Writable]]
attribute of a non-configurable property from false
to true
because, otherwise, it would allow writing any new value to the property, which makes it less strict that a non-configurable non-writable property.
Ideally, the job of the ECMAScript committee is to allow for features that don't make a given feature useless. In allowing changing [[Writable]]
from true
to false
for a non-configurable property, all the non-configurability of the property is preserved — the [[Configurable]]
attribute set to false
isn't useless.
In fact, it becomes even more non-configurable as now we couldn't even change the [[Value]]
and [[Writable]]
attributes.
However, if a non-configurable data property is additionally non-writable as well, i.e. its [[Writable]]
attribute is false
, it means that the property couldn't be assigned a new value.
Hence, once again, sensibly this non-configurable nature of the property, together with its non-writable nature, disallows changing its [[Value]]
attribute to any other value or its [[Writable]]
attribute to true
.
How to remember all of this?
No doubt in the fact that there is just a lot of information above to digest in one single go.
At this point, if you feel that you couldn't remember even a single thing what's stated above such as when could we change [[Writable]]
to true
from false
, or whether we could redefine [[Set]]
for a non-configurable property, then here's a simplistic approach to remember everything.
Property attributes sound easier than read. They are an extremely simple concept and remembering how they work shouldn't be difficult at all!
Yup, that's right.
In the previous sections, we had to explain each and every aspect of all the given property attributes, each of which encompasses multiple cases. Showing all these cases along with the code snippets leads to quite long and cluttered text.
If we consolidate everything together, it might seem way more easier to digest how property attributes work.
Let's do it...
- When a property is writable (regardless of the value of other attributes), we could change its
[[Value]]
manually or via a property assignment expression, for e.g.o.p = 10
. Simple! - When a property is non-writable (regardless of the value of other attributes), we could not change its
[[Value]]
via a property assignment expression, for e.g.o.p = 10
. Simple! - When a property is configurable and writable, essentially all operations are possible on the property. Simple!
- When a property is configurable but non-writable, we couldn't use a property assignment expression, for e.g.
o.p = 10
, to change its value. This follows from 2). However, all other operations are still possible on the property including manually changing[[Value]]
usingObject.defineProperty()
. This follows from 3). Simple! - When a property is non-configurable and an accessor property, no attribute could be changed. Simple!
- When a property is non-configurable and non-writable, no attribute could be changed. Simple!
- When a property is non-configurable but writable, no attribute could be changed except for
[[Value]]
, as mentioned in 1), and[[Writable]]
, to make the property even more strict. Simple!
And this ends this sufficiently long discussion.
Spread the word
Think that the content was awesome? Share it with your friends!
Join the community
Can't understand something related to the content? Get help from the community.