SmaGal
SmaGal

Reputation: 135

Is there any way to turn an existing Javascript object into an array without creating a new separate array?

There are a lot of mentions on differentes readings that arrays are a special class of object in Javascript. For example here:

https://www.codingame.com/playgrounds/6181/javascript-arrays---tips-tricks-and-examples

So, and since an object is a collection of properties (or keys) and values, I was thinking if there is a way to start with an object and ends with an array (in the sense that the method Array.isArray() returns true for that object emulating an array). I have started looking at the arrays properties:

let arr = [0, 1, 2, 3, 4, 5];
console.log(Object.getOwnPropertyNames(arr));
console.log(Array.isArray(arr));

So I tried to emulate the same using an object:

let arrEmulation = {0:0, 1:1, 2:2, 3:3, 4:4, 5:5, "length":6};
console.log(Object.getOwnPropertyNames(arrEmulation));
console.log(Array.isArray(arrEmulation));

But Array.isArray(arrEmulation) still returns false. First, I want to apologize if this is an stupid question, but is there any way I can manually convert an object to array adding special properties (or keys) to it?

Please, note I don't want to know how to convert object to array, I just want to understand which are those special things that make possible to interpret an object like an array.

Upvotes: 6

Views: 1053

Answers (3)

Black Mamba
Black Mamba

Reputation: 15545

Javascript is all about prototypal inheritance:

Prototype Inheritance All JavaScript objects inherit properties and methods from a prototype:

Date objects inherit from Date.prototype Array objects inherit from Array.prototype Person objects inherit from Person.prototype The Object.prototype is on the top of the prototype inheritance chain:

Date objects, Array objects, and Person objects inherit from Object.prototype.

As seen here isArray is an function in prototype chain of the Array object.

An polyfill as suggested in MDN Array.isArray() alternate if isArray is not present is:

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

So the type is determined by the prototype chain instead of what value it returns.

Similarly, as per Tio Zed's answer

const newArray = Array.from(arrEmulation) // [0, 1, 2, 3, 4, 5]
Array.isArray(newArray)

What it really does is just change to prototype from that of object to that of an Array.

A deeper go through of isArray thanks @Kaiido for making me dig deeper. The Array is array checks these there points

If Type(arg) is not Object, return false. If the value of the [[Class]] internal property of arg is "Array", then return true. Return false.

And

Array instances inherit properties from the Array prototype object and their [[Class]] internal property value is "Array". Array instances also have the following properties.

Upvotes: 2

CertainPerformance
CertainPerformance

Reputation: 370689

I don't think it's possible, in the strictest sense, given the standard specification. Looking up Array.isArray:

If the value of the [[Class]] internal property of arg is "Array", then return true.

So, for Array.isArray(arrEmulation) to return true, you must somehow modify the [[Class]] of the object to be Array, rather than Object. But, looking at ES5's 8.6.2 Object Internal Properties and Methods regarding [[Class]]:

Note: This specification defines no ECMAScript language operators or built-in functions that permit a program to modify an object’s [[Class]] or [[Prototype]] internal properties or to change the value of [[Extensible]] from false to true. Implementation specific extensions that modify [[Class]], [[Prototype]] or [[Extensible]] must not violate the invariants defined in the preceding paragraph.

Also:

Note that this specification does not provide any means for a program to access that value except through Object.prototype.toString

So, the official specification doesn't provide a way to do it in ES5 - if there was a way to do it, it would be non-standard and implementation dependent.

That said, unless you absolutely need to use Array.isArray or have Object.prototype.toString.call(arrEmulation) to return [object Array], you can still use Object.setPrototypeOf to set the prototype of arrEmulation to Array.prototype, allowing you to use array methods on the object and have instanceof Array return true:

const arrEmulation = {0:0, 1:1, 2:2, "length":6};
Object.setPrototypeOf(arrEmulation, Array.prototype);

console.log(arrEmulation instanceof Array);
arrEmulation.forEach((value) => {
  console.log(value);
});
// Internal [[Class]] property is still `Object`, though:
console.log(Object.prototype.toString.call(arrEmulation));
// Unlike a true array:
console.log(Object.prototype.toString.call([]));

console.log('-----');

// although you can set the `toStringTag` to the string 'Array' in ES6+,
// it is cosmetic only and does not pass an `Array.isArray` test:
arrEmulation[Symbol.toStringTag] = 'Array';
console.log(Object.prototype.toString.call(arrEmulation));
console.log(Array.isArray(arrEmulation));

But note that you should avoid using Object.setPrototypeOf in real code:

Warning: Changing the [[Prototype]] of an object is, by the nature of how modern JavaScript engines optimize property accesses, a very slow operation, in every browser and JavaScript engine. The effects on performance of altering inheritance are subtle and far-flung, and are not limited to simply the time spent in the Object.setPrototypeOf(...) statement, but may extend to any code that has access to any object whose [[Prototype]] has been altered. If you care about performance you should avoid setting the [[Prototype]] of an object. Instead, create a new object with the desired [[Prototype]] using Object.create().

(of course, Object.create involves creating a new object, which is different from what you want to do, which is to change the existing arrEmulation object)

There doesn't look to be a way to do it in ES6+ either - its text is somewhat similar, but not identical. Specifically, for Array.isArray to return true, the object in question needs to be an "Array exotic object" (or a Proxy that points to one) - but setPrototypeOf only sets the prototype, neither it nor any other method can make the object actually become an Array exotic object (which looks like it has to be natively constructed by the interpreter, and is not emulatable enough).

Upvotes: 6

Tio Zed
Tio Zed

Reputation: 25

You can convert anything that is close enough to an array by using Array.from(). In your example, we could just call:

const arrEmulation = {0:0, 1:1, 2:2, 3:3, 4:4, 5:5, length: 6};

const newArray = Array.from(arrEmulation) // [0, 1, 2, 3, 4, 5]
Array.isArray(newArray) // true

Upvotes: -2

Related Questions