0
let a = {0: 'a', 1: 'b', length: 2}
Array.apply(null, a) // ['a', 'b']

Using the Array constructor is the fastest way to convert an Array-like Object to Array e.g. jsperf

I want to figure out how it works but I failed. In ECMAScript-262, I can't find the corresponding approach to explain that code.

Why the Array constructor accept an array-like object can turn it to an Array.

Difference between Array.apply(null, Array(x) ) and Array(x)

Why does Array.apply(null, [args]) act inconsistently when dealing with sparse arrays?

Eriice
  • 147
  • 1
  • 7
  • Since you mentioned ECMAScript, your answer relies here: https://tc39.es/ecma262/#sec-array-items . In a nutshell, `Array.apply(null, a);` is exactly like invoking the constructor with `...items`. I'm not exactly sure whether that constructor signature is invoked or whether the constructor that accepts ...items and length is invoked, though. – briosheje Aug 14 '19 at 14:52

2 Answers2

1

With apply() you can call a function and pass the arguments that should be used as an array-like object.

So Array.apply(null, {0: 'a', 1: 'b', length: 2}) is equivalent to Array('a','b')

And as an Array can be constructed using (MDN - Array):

new Array(element0, element1[, ...[, elementN]])

And as an array belongs to those objects that can be constructed without new, the given code will construct an array with those elements.

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • Thank for your help:) From my new test, Array.apply(null, '123')` will throw an `CreateListFromArrayLike called on non-object` error, So, it seems it would call the CreateListFromArrayLike Function in the constructor, similarly, I can't find any description in it. – Eriice Aug 14 '19 at 15:27
  • @Eriice the issue there is exactly as the message suggests - you don't have an *object*. You are passing a primitive, not something that has numeric keys and a `length` property. You *can* have array access on a string, e.g., `"abc"[0]` but that will actually cast the string *primitive* to an *object* and only for the operation. So if you pass a String *object* using `.apply` that will work `Array.apply(null, new String("123"))` – VLAZ Aug 16 '19 at 10:50
  • @VLAZ Thank you, I have figure out it. The reason why `Array.from('123')` can work is the inside function will convert `123` to `String{"123"}` by `Object('123')` – Eriice Aug 16 '19 at 11:50
  • @Eriice yes, `Array.from` works differently to `Array.apply` as well as just calling `Array()`. – VLAZ Aug 16 '19 at 12:00
  • 1
    @Eriice the difference is that `Array.from()` accepts array-like object or iterable object. `Function.prototype.apply()` accepts only array-like object. A string has `[@@iterator]()` and counts as iteratable object. [22.1.2.1Array.from](https://www.ecma-international.org/ecma-262/6.0/#sec-array.from) 4-6 applies for strings in the current specs. [7.3.17 CreateListFromArrayLike](https://www.ecma-international.org/ecma-262/6.0/#sec-createlistfromarraylike) that is used in the apply function on the other hand has `If Type(obj) is not Object, throw a TypeError exception.`. – t.niese Aug 16 '19 at 13:31
  • 1
    @Eriice the `Object(arrayLike)` does not apply to the string (even though it would have the same effect) , this is misleading in the polyfill of [MDN: Array.from - Polyfill](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Polyfill). That strings are iterable was added at the same time when `Array.from` was introduced. – t.niese Aug 16 '19 at 13:44
  • @t.niese You are right. I make a mistake, I thought the string will turn to String as an array-like Object in the `Array.from`. – Eriice Aug 18 '19 at 06:03
1

When using Function#apply(), the second parameter takes an array-like. An array-like is basically an object that has numeric keys and a length property but isn't necessarily an array - for example the arguments object is an array-like.

That parameter will then be supplied to the function you call apply on as if it is all the arguments for that function:

function foo(one, two, three) {
  console.log("one:", one);
  console.log("two:", two);
  console.log("three:", three);
}
//normal invocation
foo("hello", "world", "!");

//.apply using an array-like
foo.apply(null, {0: "nice", 1: "meeting", 2: "you", length: 3});

//.apply using an array
foo.apply(null, ["see", "you", "later"]);

So, when you call Array.apply(null, {0: 'a', 1: 'b', length: 2}) that is equivalent to the call Array('a', 'b') - using the array constructor with multiple arguments produces an array from those arguments:

console.log(Array("a", "b"));

Thus when you call apply on the constructor function you get that behaviour.

In ES6, passing an array as a second argument to .apply is almost the same as using the spread syntax:

function foo(one, two, three) {
  console.log("one:", one);
  console.log("two:", two);
  console.log("three:", three);
}

const arrayArgs = ["hello", "world", "!"];
foo(...arrayArgs);

However, this doesn't work with array-likes:

function foo(one, two, three) {
  console.log("one:", one);
  console.log("two:", two);
  console.log("three:", three);
}

const arrayLikeArgs = {0: "hello", 1: "world", 2: "!", length: 3};
foo(...arrayLikeArgs);
VLAZ
  • 26,331
  • 9
  • 49
  • 67