我可以在不使用数组构造函数或数组文字的情况下创建一个 Array.isArray() returns 为真的对象吗?

Can I create an object for which Array.isArray() returns true without using the Array constructor or array literal?

通过将其原型设置为 Array.prototype:

,我可以轻松地使一个普通对象看起来像一个数组
const obj = {};
Reflect.setPrototypeOf(obj, Array.prototype);

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

我想做Array.isArray(obj)returntrue(当然不用修改Array.isArray()方法)。 MDN polyfill for Array.isArray()如下:

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

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

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

现在 Array.isArray() returns true 用于 obj(请忽略 none 不支持 Array.isArray() 确实支持 Symbol.toStringTag)。但是,原生 Array.isArray() 函数仍然 returns false for obj。我查看了 ECMAScript 2017 规范,它说 Array.isArray() uses the abstract operation IsArray,如果参数是 Array 奇异对象,则 returns true。如果参数是 Proxy,它会直接在目标对象上调用 IsArray,因此似乎使用 Proxy 在这里无济于事。

有什么办法可以让Array.isArray(obj)returntrue?明确地说,我不想修改 Array.isArray() 或任何其他内置对象。

这与Can you fake out Array.isArray() with a user-defined object?基本相同的问题,但5年前有人问过,答案是基于ECMAScript 5规范。我正在寻找基于 ECMAScript 2017 规范的答案。

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

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

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

如果您的实际问题是“我可以将任意对象转换为数组奇异对象吗?”,答案是肯定的.

一个对象要么创建为奇异数组,要么不创建,您无法更改它。

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

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

Proxy 对象不会是数组,但会被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]

不,你不能。您可以很容易地创建类似对象的数组,但与 .push() 或其他自行修改 length 属性 的数组方法不同,您永远无法创建 length 属性当你在像 nodeListHTMLCollection.

这样的奇异数组中喜欢 a[a.length] = "test" 时,它会自行增加

就我所能创建的 Frankenstarray 而不是使用适当的数组来扩展而是使用适当的对象而言。

Babel 可以将 class MyObject extends Array 转换为导致 Array.isArray(new MyObject) 到 return true 的 ES5 代码: Live demo

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()));