我可以创建一个 Array.isArray() 返回 true 的对象而不使用 Array 构造函数或数组文字吗?



我可以通过将其原型设置为Array.prototype来轻松地使普通对象看起来像数组:

const obj = {};
Reflect.setPrototypeOf(obj, Array.prototype);

(我知道魔术length属性和稀疏数组也存在一些问题,但这不是这个问题的重点。

我想Array.isArray(obj)返回true(当然不修改Array.isArray()方法)。Array.isArray()的 MDN 填充如下:

if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}

通过使用Symbol.toStringTag属性,我可以Object.prototype.toString.call(obj)返回'[object Array]'

obj[Symbol.toStringTag] = 'Array';
console.log(Object.prototype.toString.call(obj) === '[object Array]'); // true

现在,填充Array.isArray()返回true用于obj(请忽略不支持Array.isArray()的浏览器都不支持Symbol.toStringTag的事实)。但是,本机Array.isArray()函数仍返回falseforobj。我查看了 ECMAScript 2017 规范,它说Array.isArray()使用抽象操作IsArray,如果参数是数组外来对象,则返回true。如果参数是代理,它会直接在目标对象上调用IsArray,因此使用代理似乎在这里无济于事。

有什么办法让Array.isArray(obj)回报true吗?为了清楚起见,我不想修改Array.isArray()或任何其他内置对象。

这基本上与你能用用户定义的对象伪造 Array.isArray() 的问题相同?,但它是在 5 年前提出的,答案是基于 ECMAScript 5 规范的。我正在寻找基于 ECMAScript 2017 规范的答案。

不,正如您已经说过的那样,一个真正的数组(这是Array.isArray检测到的)是一个数组奇异对象,这意味着它的.length以特殊的方式运行。

构造的唯一方法是使用数组构造函数或Array的子类(反过来调用数组构造函数)或来自另一个领域的相同方法。还有无数其他方法返回新的数组(例如String::splitString::matchArray.fromArray.ofArray原型方法、Object.keysObject.getOwnPropertyNames)。
此外,用于标记模板或作为代理应用/构造陷阱的函数将接收全新的数组,并且数组也作为Promise.all.entries()迭代器的结果的一部分构造。

如果您正在寻找创建数组的语法方法,数组文字将是你的主要选择,但解构表达式(在数组文字或函数中)也可以从迭代器创建数组。

如果你的实际问题是">我可以把一个任意对象变成一个数组的奇异对象吗?",答案是坚定的否定

一个对象要么被创建为一个外来数组,要么不是,你不能改变它。

但是,正如您提到的,您可以创建一个 Proxy 对象,其目标是数组。

console.log(Array.isArray(new Proxy([], {}))) // true

代理对象不会是数组,但会被Array.isArray视为数组。它将是一个新的、不同的对象,但您可以将所有内部操作重定向到所需的对象,从而有效地成为实时克隆。

function redirect(trap) {
return (target, ...args) => Reflect[trap](obj, ...args);
}
var obj = {0:0, 1:1, length:2};
var arrayified = new Proxy([], {
apply: redirect('apply'),
construct: redirect('construct'),
defineProperty: redirect('defineProperty'),
deleteProperty: redirect('deleteProperty'),
enumerate: redirect('enumerate'),
get: redirect('get'),
getOwnPropertyDescriptor: redirect('getOwnPropertyDescriptor'),
getPrototypeOf: redirect('getPrototypeOf'),
has: redirect('has'),
isExtensible: redirect('isExtensible'),
ownKeys: redirect('ownKeys'),
preventExtensions: redirect('preventExtensions'),
set: redirect('set'),
setPrototypeOf: redirect('setPrototypeOf')
});
console.log(arrayified); // [0, 1]

Babel 可以将class MyObject extends Array转换为 ES5 代码,导致Array.isArray(new MyObject)返回true: 现场演示

ES2015

class MyObject extends Array{};
console.log(Array.isArray(new MyObject))

由 babel 生成的 ES5

"use strict";
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return call && (typeof call === "object" || typeof call === "function")
? call
: self;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError(
"Super expression must either be null or a function, not " +
typeof superClass
);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
var MyObject = (function(_Array) {
_inherits(MyObject, _Array);
function MyObject() {
return _possibleConstructorReturn(
this,
(MyObject.__proto__ || Object.getPrototypeOf(MyObject))
.apply(this, arguments)
);
}
return MyObject;
})(Array);
console.log(Array.isArray(new MyObject()));

不,你不能。您可以非常轻松地创建类似数组的对象,但与自己修改length属性的.push()或其他数组方法不同,当您喜欢在奇异的数组(如nodeListHTMLCollection)中a[a.length] = "test"时,您永远无法使length属性自行增加。

这是我所能创建的弗兰肯斯特数组,而无需使用适当的数组来扩展,而是使用适当的对象。

最新更新