An object is a collection of properties and a link to a prototype. Inheritance works by
following that link. The operations on an object are defined as internal methods
(ECMA-262, Object Internal Methods and Internal Slots), which a
Proxy can intercept.
Properties
A property key is a String or a Symbol. Any other value used as a key is
converted to a string, so
obj[1] and obj["1"] are the same property.
Key order
Own keys are returned in this order (OrdinaryOwnPropertyKeys):
- Array indices in ascending numeric order. An array index is a
canonical numeric string from
"0"to 232 − 2. - Other string keys, in the order they were created.
- Symbol keys, in the order they were created.
Object.keys, JSON.stringify and for…in’s own
keys follow this order, leaving out symbols.
Property attributes
A property is either a data property or an accessor property (Property Attributes).
| Attribute | Applies to | Meaning | Default from defineProperty |
|---|---|---|---|
[[Value]] | Data | The value | undefined |
[[Writable]] | Data | Whether assignment changes the value | false |
[[Get]] | Accessor | Function called on read | undefined |
[[Set]] | Accessor | Function called on write | undefined |
[[Enumerable]] | Both | Whether for…in and Object.keys see it | false |
[[Configurable]] | Both | Whether it can be deleted, or its attributes changed | false |
A property made by assignment or an object literal is writable, enumerable and configurable.
const o = {};
Object.defineProperty(o, 'id', { value: 1, enumerable: true });
Object.getOwnPropertyDescriptor(o, 'id');
// { value: 1, writable: false, enumerable: true, configurable: false }
o.id = 2; // no effect; TypeError in strict code
Object.keys(o); // ['id']
- A descriptor passed to
definePropertymay not have bothvalueorwritableandgetorset. - While a property is non-configurable, it cannot be deleted, cannot switch kind, and
cannot change its
enumerable. Itswritablecan only go from true to false. Object.definePropertiesandObject.getOwnPropertyDescriptorswork on many at once.
Getters and setters
const box = {
_w: 1,
get width() { return this._w; },
set width(v) { this._w = Math.max(0, v); },
};
box.width = -5;
box.width; // 0
A getter takes no parameters and a setter takes exactly one. With no
setter, a write is ignored in sloppy code and throws a TypeError in strict
code. this in an accessor is the receiver, the object the access
started from.
The prototype chain
Every object has an internal [[Prototype]], either an object or
null. Object.getPrototypeOf reads it, and
Object.setPrototypeOf changes it.
| Operation | Effect of the chain |
|---|---|
Read o.p | Looks at o, then each prototype. If none has it, the value is undefined |
Write o.p = v | If an accessor is found up the chain, calls its setter with this as o. If a non-writable property is found, the write fails. Otherwise it creates or updates a property on o |
delete o.p | Own properties only |
p in o | Looks along the chain |
Object.hasOwn(o, p) | Own properties only (ES2022) |
const animal = { speak() { return this.name + ' speaks'; } };
const dog = Object.create(animal);
dog.name = 'Rex';
dog.speak(); // "Rex speaks": found on the prototype
Object.getPrototypeOf(dog) === animal; // true
Object.create(null); // no prototype at all, no inherited methods
- A function’s
prototypeproperty becomes the prototype of objects created bynew. That is not the function’s own[[Prototype]], which isFunction.prototype. - The chain must not form a cycle:
setPrototypeOfthrows aTypeErrorif it would. __proto__is an accessor onObject.prototype, by Annex B. An object made withObject.create(null)does not have it.- The chain usually ends at
Object.prototype, whose prototype isnull.
Locking an object
| Method | Add properties | Delete | Change values | Test |
|---|---|---|---|---|
Object.preventExtensions | No | Yes | Yes | Object.isExtensible |
Object.seal | No | No | Yes | Object.isSealed |
Object.freeze | No | No | No | Object.isFrozen |
These apply only to the object’s own properties, so nested objects stay mutable. A non-extensible object’s prototype cannot be changed either.
Internal methods and exotic objects
Every object implements these essential internal methods. An object that uses the
default behaviour is ordinary, and one that overrides any of them is
exotic. Functions add [[Call]], and constructors add
[[Construct]].
| Internal method | Purpose | Proxy trap | Reflect function |
|---|---|---|---|
[[GetPrototypeOf]] | Read the prototype | getPrototypeOf | getPrototypeOf |
[[SetPrototypeOf]] | Change the prototype | setPrototypeOf | setPrototypeOf |
[[IsExtensible]] | Whether new properties may be added | isExtensible | isExtensible |
[[PreventExtensions]] | Stop new properties | preventExtensions | preventExtensions |
[[GetOwnProperty]] | Read a descriptor | getOwnPropertyDescriptor | getOwnPropertyDescriptor |
[[DefineOwnProperty]] | Create or change a property | defineProperty | defineProperty |
[[HasProperty]] | The in test | has | has |
[[Get]] | Read | get | get |
[[Set]] | Write | set | set |
[[Delete]] | Remove | deleteProperty | deleteProperty |
[[OwnPropertyKeys]] | List own keys | ownKeys | ownKeys |
[[Call]] | Call | apply | apply |
[[Construct]] | Use with new | construct | construct |
The exotic objects in the standard are arrays (a magical length),
String objects (index properties for characters), arguments objects, bound functions,
typed arrays, module namespace objects, Object.prototype (an immutable
prototype) and proxies.
Proxy
new Proxy(target, handler) creates an object whose internal methods call
the matching handler functions, called traps (ES2015). A missing trap
forwards to the target. Proxy.revocable returns
{ proxy, revoke }, and calling revoke() makes every
later operation on the proxy throw a TypeError.
const logged = new Proxy({ a: 1 }, {
get(target, key, receiver) {
console.log('read', String(key));
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
});
Reflect functionInvariants
A trap may not lie about a non-configurable property or a non-extensible
target (Invariants of the Essential Internal Methods). If it does, a
TypeError is thrown. In summary:
getmust return the actual value of a non-writable, non-configurable data property, andundefinedfor a non-configurable accessor with no getter.setmay not report success for a change to a non-writable, non-configurable data property, or to a non-configurable accessor with no setter.hasanddeletePropertymay not hide a non-configurable own property, or any own property of a non-extensible target.definePropertymay not add a property to a non-extensible target, or report one as non-configurable that the target does not have that way.getOwnPropertyDescriptormust report non-configurable properties faithfully.ownKeysmust return strings and symbols with no duplicates, including all non-configurable own keys, and exactly the target’s keys if it is non-extensible.getPrototypeOfandisExtensiblemust match the target where it is non-extensible, andconstructmust return an object.
Reflect
Reflect is a plain object with one function per internal method, with
the same names as the proxy traps (ES2015). Unlike the
Object equivalents, those that change something return a boolean instead of
throwing, and the functions accept a receiver.
Reflect.has(obj, 'x'); // like 'x' in obj
Reflect.ownKeys(obj); // strings and symbols
Reflect.get(obj, 'x', receiver); // getter runs with this = receiver
Reflect.defineProperty(obj, 'y', {}); // false on failure
Reflect.construct(F, args, G); // new.target is G