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

时间:2016-12-15 17:34:14

标签: javascript arrays ecmascript-6 ecmascript-next

通过将原型设置为Array.prototype

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

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

我想让Array.isArray(obj)返回true(当然不会修改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)返回'[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返回obj。我查看了ECMAScript 2017规范,它说Array.isArray()使用抽象操作IsArray,如果参数是Array外来对象,则返回true。如果参数是一个代理,它会直接在目标对象上调用IsArray,所以似乎使用代理在这里没有帮助。

有没有办法让Array.isArray(obj)返回true?为了说清楚,我不想修改Array.isArray()或任何其他内置对象。

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

4 个答案:

答案 0 :(得分:6)

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

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

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

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

答案 1 :(得分:1)

对象要么创建为异形数组,要么不能创建。

但是,正如您所提到的,您可以创建一个目标为数组的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]




答案 2 :(得分:0)

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

This就是我可以创建一个Frankenstarray,而不使用正确的数组来扩展,而是使用适当的对象。

答案 3 :(得分:0)

Babel可以将class MyObject extends Array转换为导致Array.isArray(new MyObject)返回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()));