In this blog post, we take a closer look at how the ECMAScript specification sees JavaScript objects. In particular, properties are not atomic in the spec, but composed of multiple attributes (think fields in a record). Even the value of a data property is stored in an attribute!
In the ECMAScript specification, an object consists of:
Internal slots, which are
storage locations that are not accessible from JavaScript, only to operations in the specification.
A collection of properties. Each property associates a key with attributes (think fields in a record).
Internal slots
This is how the specification describes internal slots (the emphasis is mine):
Internal slots correspond to internal state that is associated with objects and used by
various ECMAScript specification algorithms.
Internal slots are not object properties and they are not inherited.
Depending upon the specific internal slot specification, such state may consist of values:
of any ECMAScript language type or
of specific ECMAScript specification type values.
Unless explicitly specified otherwise, internal slots are allocated as part of the process of creating an object and may not be dynamically added to an
object.
Unless specified otherwise, the initial value of an internal slot is the value undefined.
Various algorithms within this specification create objects that have internal slots. However, the ECMAScript language provides no direct way to associate internal slots with an object.
Internal methods and internal slots are identified within this specification using names enclosed in double square brackets [[ ]].
There are two kinds of internal slots:
Method slots for manipulating objects (getting properties, setting properties, etc.)
Data slots with storage (listed in the table below)
Internal data slot
Type
[[Prototype]]
null ¦ object
[[Extensible]]
boolean
[[PrivateFieldValues]]
List of entries
Descriptions for these data slots:
[[Prototype]] stores the prototype of an object.
Can be changed via Object.getPrototypeOf() and Object.setPrototypeOf()
[[Extensible]] indicates if it is possible to add properties to an object.
Can be set to false via Object.preventExtensions().
[[PrivateFieldValues]] is used to manage private class fields.
Property
keys
The key of a property is either:
A string
A symbol
Property attributes
There are two kinds of properties and they have different attributes:
A data property stores data. Its attributes value holds any JavaScript value.
An accessor property has a getter function and/or a setter function. The former is stored in the attribute get, the latter
in the attribute set.
The following table lists all property attributes.
Kind of property
Name and type of attribute
Default value
Data property
value: any
undefined
writable: boolean
false
Accessor property
get(): any
undefined
set(v: any): void
undefined
All properties
configurable: boolean
false
enumerable: boolean
false
We have already encountered the attributes value, get, and set. The other attributes work as follows:
writable determines if the value of a data property can be changed.
configurable determines if the attributes of a property can be changed. If it is false, then:
You cannot delete the property.
You cannot change a property from a data property to an accessor property or vice versa.
You cannot change any attribute
other than value.
However, one more attribute change is allowed: You can change writable from true to false. The rationale behind this anomaly is historical: Property .length of Arrays has always been writable and non-configurable. Allowing its writable attribute to be changed enables us to freeze Arrays.
enumerable influences some operations (such as Object.assign()). If it is false, then those operations ignore the property.
Property descriptors
A property descriptor encodes the attributes of a property as a JavaScript object. Their TypeScript interfaces look as follows.
The question marks indicate that each property is optional. If you omit a property when passing a descriptor to an operation,
then its default value is used.
Retrieving descriptors for properties
The following code retrieves the object descriptor for the data property first:
If an own property already exists, then defining it via a descriptor changes that property. On one hand that allows us to use Object.defineProperty() like assignment:
Pitfall: inherited read-only properties can’t be assigned to
If an inherited property is read-only, then we can’t use assignment to change it. The rationale is that overriding an inherited property by creating an own property can be seen as non-destructively changing the inherited property. Arguably, if a property is non-writable, we shouldn’t be able to do that.
The batch version of Object.defineProperty(). Each property of properties holds a property descriptor. The keys of the properties and their values tell Object.defineProperties what properties to create or change on obj.
First, creates an object whose prototype is proto. Then, if the optional parameter properties has been provided, adds properties to it –
in the same manner as Object.defineProperties(). Finally, returns the result. For example, the following code snippet produces the same result as the previous snippet:
Returns an object where each property key 'k' of obj is mapped to the property descriptor for obj.k. The result can
be used as input for Object.defineProperties() and Object.create().
Using desc() in line A is a work-around so that .deepEqual() works.
Use cases for Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors(): copying properties into an object
Since ES6, JavaScript already has had a tool method for copying properties: Object.assign(). However,
this method uses simple get and set operations to copy a property whose key is key:
target[key] = source[key];
That means that it only creates a faithful copy of a property if:
Its attribute writable is true and its attribute enumerable is true (because that’s how assignment creates properties).
It is a data property.
The following example illustrates this limitation. Object source has a setter whose key is data.
const source = {
setdata(value) {
this._data = value;
}
};
const desc = Object.getOwnPropertyDescriptor.bind(Object);
assert.deepEqual(
Object.getOwnPropertyDescriptor(source, 'data'),
{
get: undefined,
set: desc(source, 'data').set,
enumerable: true,
configurable: true,
});
// Because there is only a setter, property `data` exists,// but has the value `undefined`.
assert.equal('data'in source, true);
assert.equal(source.data, undefined);
If we use Object.assign() to copy property
data, then the accessor property data is converted to a data property:
A method that uses super is firmly connected with its home object (the object it is stored in). There is currently no way to copy or move such a method to a
different object.
A JavaScript object has properties associated with it. A property of an object can be explained as a variable that is attached to the object. Object properties are basically the same as ordinary JavaScript variables, except for the attachment to objects.
How many types of object properties are there in JavaScript?
Properties are identified using key values. A key value is either a String value or a Symbol value. There are two types of object properties: The data property and the accessor property.
What are properties of object properties?
Object properties differentiate objects from other objects. The basic properties of an object are those items identified by its four-part name (name, type, instance, and version) and also include owner, status, platform, and release.
How do you define object properties?
defineProperty() The static method Object. defineProperty() defines a new property directly on an object, or modifies an existing property on an object, and returns the object.