9

I can easily make a plain object look like an array by setting its prototype to Array.prototype:

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

(I'm aware that there are also some problems with the magic length property and sparse arrays, but that's not the point of this question.)

I want to make Array.isArray(obj) return true (of course without modifing the Array.isArray() method). The MDN polyfill for Array.isArray() is as follows:

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

By using the Symbol.toStringTag property I can make Object.prototype.toString.call(obj) return '[object Array]':

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

Now the polyfilled Array.isArray() returns true for obj (please ignore the fact that none of the browsers that doesn't support Array.isArray() does support Symbol.toStringTag). However, the native Array.isArray() function still returns false for obj. I looked at the ECMAScript 2017 specification and it says that Array.isArray() uses the abstract operation IsArray, which returns true if the argument is an Array exotic object. If the argument is a Proxy, it calls IsArray directly on the target object, so it seems that using a Proxy wouldn't help here.

Is there any way to make Array.isArray(obj) return true? To make it clear, I don't want to modify Array.isArray() or any other build-in objects.

This is basically the same question as Can you fake out Array.isArray() with a user-defined object?, but it was asked 5 years ago, and the answers are based on the ECMAScript 5 specification. I'm looking for an answer based on the ECMAScript 2017 specification.

asked Dec 15, 2016 at 17:34
2
  • 2
    Why do you want to do this? Commented Dec 15, 2016 at 17:36
  • @ExplosionPills I'm just curious if it's possible. Commented Dec 15, 2016 at 17:38

4 Answers 4

6

No, as you already said a real array (which is what Array.isArray detects) is an array exotic object, which means that its .length behaves in a special way.

The only ways to construct that are using the array constructor or a subclass of Array (that in turn calls the array constructor) or the same from another realm. Also countless other methods return new arrays (e.g String::split, String::match, Array.from, Array.of, the Array prototype methods, Object.keys, Object.getOwnPropertyNames).
Furthermore, functions that are used for tagged templates or as proxy apply/construct traps will receive brand new arrays, and arrays also are constructed as part of the result of Promise.all or .entries() iterators.

If you're looking for syntactic ways to create arrays, the array literal will be your primary choice, but also destructuring expressions (in array literals or functions) can create arrays from iterators.

If your actual question was "Can I turn an arbitrary object into an array exotic object?", the answer is a firm No.

answered Dec 15, 2016 at 17:41

Comments

2

An object either is created as an exotic array or not, and you can't change that.

However, as you mention, you can create a Proxy object whose target is an array.

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

The Proxy object won't be an array, but will be considered an array by Array.isArray. It will be a new, different object, but you can redirect all the internal operations to the desired object, effectively becoming a live clone.

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]

answered Dec 15, 2016 at 17:43

2 Comments

I could as well use a regular array, without a Proxy. The whole point is to do this using a plain object.
With a proxy you can redirect the internal operations to the desired object
2

Babel can transform a class MyObject extends Array into ES5 code that causes Array.isArray(new MyObject) to return true: Live demo

ES2015

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

ES5 generated by babel

"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()));
answered Sep 20, 2017 at 11:32

3 Comments

That's not what my question is about. I wanted to find out, as Bergi phrased it, if I can turn an arbitrary object into an array exotic object.
I answered the question in the title, for which people might still look for an answer, even if it doesn't help you.
This is still using the Array constructor, just indirectly.
0

No you can not. You can create array like objects quite easily but unlike .push() or other array methods which modify the length property by themselves, you can never make the length property increase by itself when you do like a[a.length] = "test" in an exotic array like a nodeList or HTMLCollection.

This is as far as i could get creating a Frankenstarray without using a proper array to extend but a proper object instead.

answered Dec 16, 2016 at 10:01

1 Comment

"you can never make the length property increase by itself" You can, by using Proxy.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.