Javascript 是否可以被骗以为对象是数组?

Can Javascript be tricked into believing an Object is an Array?

从对象文字 {}new Object() 开始,是否有任何方法可以修改实例,使其表现得像 Array exotic object

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

我们可以通过原型继承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

显然,Object.setPrototypeOf() 为我们提供了数组的一些 功能,但并不是所有的不变量都得到维护,并且它的记录和序列化方式也不同。 我们是否可以对我们的对象实例做进一步的调整,使它的行为更像一个奇异的数组对象?

创建一个 class 的实例,extends Array 给出更好的结果:

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

原型继承通过 extends 与通过 setPrototypeOf 的工作方式有什么不同吗?除了 setPrototypeOf 之外,是否还有其他效果可以应用于 Object 实例,以获得与我们使用 extends 获得的结果相似的结果?

Is there something different about how prototypical inheritance works via extends versus via setPrototypeOf?

是的。 setPrototypeOf 事后更改原型,因此原始值是一个“普通”对象。对于 extends Array,该值实际上是 Array 的一个实例,因为它是调用 Array 构造函数的结果。

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?

是也不是。

奇异数组对象对内部插槽有不同的实现[[DefineOwnProperty]],您不能直接从“用户空间”代码更改内部插槽。

但是您可以使用 Proxy 拦截 属性 赋值并实现与 [[DefineOwnProperty]].

相同的行为

Are there further tweaks we can do to our object instance to make it behave even more like an Array exotic object?

没有。对象的类型不能事后更改。它永远不会成为一个函数,它永远不会成为一个代理外来对象,它永远不会成为一个数组外来对象。 Array.isArray(obj)永远不会变成true

要创建数组奇异对象,您必须使用 new Array、数组文字语法或 Reflect.construct(即 )来构造它。

要创建一个行为类似于数组但不是数组的对象,您可以使用 Proxy.

要使不是数组的对象具有与数组类似的 .length 行为,您可以定义一个(非常低效的)getter/setter 来维护不变量。