Starting from an object literal {}
or new Object()
, is there any way to modify the instance such that it behaves like an Array exotic object?
Special behaviours of Array exotics:
const a = [];
console.log(a instanceof Array); // true
console.log(a.__proto__ === Array.prototype); // true
console.log(a.length); // 0
a.push(true);
console.log(a.length); // length increases to 1
console.log(Object.hasOwnProperty(a, 'length')); // false
console.log('length' in a); // true
a[1] = true;
console.log(a.length); // length increases to 2 to fit element [1]
console.log(a); // visualized as Array [ true, true ]
console.log(JSON.stringify(a)); // serialized as "[true,true]"
a.length = 0;
console.log(a[1]); // element [1] removed because length decreased to 0
We can achieve some Array behaviours by prototypically inheriting from Array
:
const o = {};
Object.setPrototypeOf(o, Array.prototype);
console.log(o instanceof Array); // true
console.log(o.__proto__ === Array.prototype); // true
console.log(o.length); // 0
o.push(true);
console.log(o.length); // length increases to 1
console.log(Object.hasOwnProperty(o, 'length')); // false
console.log('length' in o); // true
o[1] = true;
console.log(o.length); // length remains at 1
console.log(o); // visualized as [true, 1: true]
console.log(JSON.stringify(o)); // serialized as {"0":true,"1":true,"length":1}
o.length = 0;
console.log(o[1]); // element [1] remains despite length increase
Clearly, Object.setPrototypeOf()
gives us some of the functionality of Arrays, but not all its invariants are maintained and it's logged and serialized differently. Are there further tweaks we can do to our object instance to make it behave even more like an Array exotic object?
Creating an instance of a class which extends Array
gives much better results:
const c = new class extends Array {};
console.log(c instanceof Array); // true
console.log(c.__proto__ === Array.prototype); // false
console.log(c.__proto__.__proto__ === Array.prototype); // true
console.log(c.length); // 0
c.push(true);
console.log(c.length); // length increases to 1
console.log(Object.hasOwnProperty(c, 'length')); // false
console.log('length' in c); // true
c[1] = true;
console.log(c.length); // length increases to 2 to fit element [1]
console.log(c); // visualized as Array [ true, true ]
console.log(JSON.stringify(c)); // serialized as "[true,true]"
c.length = 0;
console.log(c[1]); // element [1] removed because length decreased to 0
Is there something different about how prototypical inheritance works via extends
versus via setPrototypeOf
? Are there effects that we can apply to an Object instance in addition to setPrototypeOf
to get similar results to what we've achieved with extends
?
Is there something different about how prototypical inheritance works via
extends
versus viasetPrototypeOf
?
Yes. setPrototypeOf
changes the prototype after the fact, so the original value is a "normal" object. With extends Array
, the value will actually be an instance of Array
, because it is the result of calling the Array
constructor.
Are there effects that we can apply to an Object instance in addition to
setPrototypeOf
to get similar results to what we've achieved withextends
?
Yes and no.
Exotic array objects have different implementation for the internal slot [[DefineOwnProperty]]
and you cannot directly change an internal slot from "user land" code.
But you could potentially use a Proxy
to intercept property assignment and implement the same behavior as [[DefineOwnProperty]]
.