我可以通过将其原型设置为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()
函数仍返回false
forobj
。我查看了 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::split
、String::match
、Array.from
、Array.of
、Array
原型方法、Object.keys
、Object.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()
或其他数组方法不同,当您喜欢在奇异的数组(如nodeList
或HTMLCollection
)中a[a.length] = "test"
时,您永远无法使length
属性自行增加。
这是我所能创建的弗兰肯斯特数组,而无需使用适当的数组来扩展,而是使用适当的对象。