Creating Arrays

Prior to ECMAScript 6, there were two primary ways to create arrays: the Array constructor and array literal syntax. Both approaches require listing array items individually and are otherwise fairly limited. Options for converting an array-like object (that is, an object with numeric indices and a length property) into an array were also limited and often required extra code. To make JavaScript arrays easier to create, ECMAScript 6 adds the Array.of() and Array.from() methods.

The Array.of() Method

One reason ECMAScript 6 adds new creation methods to JavaScript is to help developers avoid a quirk of creating arrays with the Array constructor. The new Array() constructor actually behaves differently based on the type and number of arguments passed to it. For example:

  1. let items = new Array(2);
  2. console.log(items.length); // 2
  3. console.log(items[0]); // undefined
  4. console.log(items[1]); // undefined
  5. items = new Array("2");
  6. console.log(items.length); // 1
  7. console.log(items[0]); // "2"
  8. items = new Array(1, 2);
  9. console.log(items.length); // 2
  10. console.log(items[0]); // 1
  11. console.log(items[1]); // 2
  12. items = new Array(3, "2");
  13. console.log(items.length); // 2
  14. console.log(items[0]); // 3
  15. console.log(items[1]); // "2"

When the Array constructor is passed a single numeric value, the length property of the array is set to that value. If a single non-numeric value is passed, then that value becomes the one and only item in the array. If multiple values are passed (numeric or not), then those values become items in the array. This behavior is both confusing and risky, as you may not always be aware of the type of data being passed.

ECMAScript 6 introduces Array.of() to solve this problem. The Array.of() method works similarly to the Array constructor but has no special case regarding a single numeric value. The Array.of() method always creates an array containing its arguments regardless of the number of arguments or the argument types. Here are some examples that use the Array.of() method:

  1. let items = Array.of(1, 2);
  2. console.log(items.length); // 2
  3. console.log(items[0]); // 1
  4. console.log(items[1]); // 2
  5. items = Array.of(2);
  6. console.log(items.length); // 1
  7. console.log(items[0]); // 2
  8. items = Array.of("2");
  9. console.log(items.length); // 1
  10. console.log(items[0]); // "2"

To create an array with the Array.of() method, just pass it the values you want in your array. The first example here creates an array containing two numbers, the second array contains one number, and the last array contains one string. This is similar to using an array literal, and you can use an array literal instead of Array.of() for native arrays most of the time. But if you ever need to pass the Array constructor into a function, then you might want to pass Array.of() instead to ensure consistent behavior. For example:

  1. function createArray(arrayCreator, value) {
  2. return arrayCreator(value);
  3. }
  4. let items = createArray(Array.of, value);

In this code, the createArray() function accepts an array creator function and a value to insert into the array. You can pass Array.of() as the first argument to createArray() to create a new array. It would be dangerous to pass Array directly if you cannot guarantee that value won’t be a number.

I> The Array.of() method does not use the Symbol.species property (discussed in Chapter 9) to determine the type of return value. Instead, it uses the current constructor (this inside the of() method) to determine the correct data type to return.

The Array.from() Method

Converting non-array objects into actual arrays has always been cumbersome in JavaScript. For instance, if you have an arguments object (which is array-like) and want to use it like an array, then you’d need to convert it first. To convert an array-like object to an array in ECMAScript 5, you’d write a function like the one in this example:

  1. function makeArray(arrayLike) {
  2. var result = [];
  3. for (var i = 0, len = arrayLike.length; i < len; i++) {
  4. result.push(arrayLike[i]);
  5. }
  6. return result;
  7. }
  8. function doSomething() {
  9. var args = makeArray(arguments);
  10. // use args
  11. }

This approach manually creates a result array and copies each item from arguments into the new array. That works but takes a decent amount of code to perform a relatively simple operation. Eventually, developers discovered they could reduce the amount of code by calling the native slice() method for arrays on array-like objects, like this:

  1. function makeArray(arrayLike) {
  2. return Array.prototype.slice.call(arrayLike);
  3. }
  4. function doSomething() {
  5. var args = makeArray(arguments);
  6. // use args
  7. }

This code is functionally equivalent to the previous example, and it works because it sets the this value for slice() to the array-like object. Since slice() needs only numeric indices and a length property to function correctly, any array-like object will work.

Even though this technique requires less typing, calling Array.prototype.slice.call(arrayLike) doesn’t obviously translate to, “Convert arrayLike to an array.” Fortunately, ECMAScript 6 added the Array.from() method as an obvious, yet clean, way to convert objects into arrays.

Given either an iterable or an array-like object as the first argument, the Array.from() method returns an array. Here’s a simple example:

  1. function doSomething() {
  2. var args = Array.from(arguments);
  3. // use args
  4. }

The Array.from() call creates a new array based on the items in arguments. So args is an instance of Array that contains the same values in the same positions as arguments.

I> The Array.from() method also uses this to determine the type of array to return.

Mapping Conversion

If you want to take array conversion a step further, you can provide Array.from() with a mapping function as a second argument. That function operates on each value from the array-like object and converts it to some final form before storing the result at the appropriate index in the final array. For example:

  1. function translate() {
  2. return Array.from(arguments, (value) => value + 1);
  3. }
  4. let numbers = translate(1, 2, 3);
  5. console.log(numbers); // 2,3,4

Here, Array.from() is passed (value) => value + 1 as a mapping function, so it adds 1 to each item in the array before storing the item. If the mapping function is on an object, you can also optionally pass a third argument to Array.from() that represents the this value for the mapping function:

  1. let helper = {
  2. diff: 1,
  3. add(value) {
  4. return value + this.diff;
  5. }
  6. };
  7. function translate() {
  8. return Array.from(arguments, helper.add, helper);
  9. }
  10. let numbers = translate(1, 2, 3);
  11. console.log(numbers); // 2,3,4

This example passes helper.add() as the mapping function for the conversion. Since helper.add() uses the this.diff property, you need to provide the third argument to Array.from() specifying the value of this. Thanks to the third argument, Array.from() can easily convert data without calling bind() or specifying the this value in some other way.

Use on Iterables

The Array.from() method works on both array-like objects and iterables. That means the method can convert any object with a Symbol.iterator property into an array. For example:

  1. let numbers = {
  2. *[Symbol.iterator]() {
  3. yield 1;
  4. yield 2;
  5. yield 3;
  6. }
  7. };
  8. let numbers2 = Array.from(numbers, (value) => value + 1);
  9. console.log(numbers2); // 2,3,4

Since the numbers object is an iterable, you can pass numbers directly to Array.from() to convert its values into an array. The mapping function adds one to each number so the resulting array contains 2, 3, and 4 instead of 1, 2, and 3.

I> If an object is both array-like and iterable, then the iterator is used by Array.from() to determine the values to convert.