background
In code review, I found that someone wrote a code like this:
const list = new Array(5)
list.map(() = >{... })Copy the code
Using new Array() to create an Array is problematic. Out of curiosity, I ran the above code in the console and “surprisingly” the map callback did not execute. Why? To avoid further holes in the code, I decided to explore
new Array(len)
What did
The Array constructor supports passing 1-N arguments without passing them. We will only discuss passing one argument of type positive integer. Before exploring what new Array(len) does, let’s take a look at the Array created by new Array(len) :
let arr = new Array(5)
console.log(arr)
// Output: [empty × 5]
console.log(arr.map)
ƒ map() {[native code]}
console.log(arr.length)
// Output: 5
Copy the code
As you can see from the above code, in the Array created with new Array(len), the corresponding Array methods and attributes are present, but the element in the Array is “empty”. The ECMAScript standard describes what New Array(Len) does, but to simplify it, it does a few things:
- Check if len is a valid number (a positive integer less than 2^ 32-1), and throw an error if it is not;
- Create a JavaScript Array instance;
- Set the length property of this instance object to the value of the input parameter;
The above operation is equivalent to:
let arr = [];
arr.length = 5;
Copy the code
An array object with length equal to len is returned, but the array does not contain any actual elements, and it cannot be taken for granted that it contains len elements with a value of undefined.
empty
vs undefined
As you can see in the above example, the Array created with new Array(len) outputs len empty arrays. There is no empty in the JS base datatype, so what is it? Let’s try to print it out:
console.log(new Array(5) [2])
// output: undefined
Copy the code
If I output undefined when I access “empty”, is empty undefined? The answer is no, let’s look at this example:
var arr = new Array(2);
var undefinedArr = [undefined.undefined];
console.log(undefinedArr); // [undefined, undefined]
undefinedArr[0]; // undefined
arr.forEach(item= > { console.log(item) }); / / no output
undefinedArr.forEach(item= > { console.log(item) }); // Output two undefined variables
console.log(arr.hasOwnProperty(0)) // false
console.log(undefinedArr.hasOwnProperty(0)) // true
Copy the code
Arr and undefinedArr behave identically only when accessing a single concrete element, and are mostly different in some other cases. Why arR returns undefined when accessing a specific individual element can be seen from the perspective of JS engine parsing. Empty is described as an empty object reference in v8 source code, but there is no such type in JS base type, so use undefined instead. This behavior is similar to when we access a nonexistent variable on the console, we usually get undefined. Arr doesn’t have any actual elements, so the forEach callback is not executed, and undefinedArr is populated.
Sparse arrays versus dense arrays
In fact, arR and undefinedArr subdivision should be two types of array, arR type is called sparse array, undefinedArr is called dense array. Let’s look at the definitions of these two types of arrays:
Sparse array: an array whose index is discontinuous and whose array length is greater than the number of elements, can be simply understood as an array with empty;
Dense array: an array whose index is continuous and whose array length equals the number of elements;
How do sparse arrays and dense arrays convert to each other
Sparse array => dense array:
var arr = new Array(5); // Sparse array
// ES5
Array.apply(null, arr);
// ES6
Array.form(arr);
[...arr];
Copy the code
Dense array => Sparse array:
var arr = [1.3.4.5.6]; // Dense array
arr.length = 10 // [1, 3, 4, 5, 6, empty × 5]
Copy the code
Sparse array features
-
In most methods of iterating over an array of numbers, the empty element is not called as a callback (map, forEach, filter, etc.). The same applies to the in statement.
const arr = [3.4.5] // Sparse array arr.forEach(item= > { console.log(item)}) // output: 3,4,5 console.log(arr.map(item= > { console.log(item) return item+1 })) // output: 3,4,5, [4, empty, 5, empty, 6] // It is worth noting that "empty" elements in the sparse array are still "empty" in the array returned after the map. console.log(arr1.filter(item= > item === undefined)) // Output: [] console.log(arr1.filter(item= > item > 3 )) // output: [4,5] for (var i in a) { console.log(a[i]) } // output: 3,4,5 for (var i of a) { console.log(i) } // output: 3, undefined, 4, undefined, 5 Copy the code
-
Sparse arrays access elements more slowly than dense arrays.
const arr = new Array(200000) arr[19999] = 88 console.time('using[]') arr[19999] console.timeEnd('using[]') / / using [] : 0.031982421875 ms const ddd = [...new Array(200000)] ddd[19999] = 88 console.time('using[]') ddd[19999] console.timeEnd('using[]') / / using [] : 0.010009765625 ms Copy the code
The reason is that the V8 engine uses hash mode to access objects with sparse arrays, which is slower because it requires a hash to be computed, but hash is more efficient for space utilization. While dense array, it is to apply for a continuous memory space, access can be directly through the “index” to access, so the speed is relatively fast;
-
Sparse arrays differ from dense arrays in some array methods
var a = [1,,,,,,]var b = new Array(5) var c = [] Copy the code
/ / Array. Prototype. Every () and Array. The prototype. Some () b.every(i= > i === undefined); // true a.some(i= > i === undefined); // false Copy the code
We said that the method of iterating through a number group skips the “empty” element. So if the empty element is not found in the array, some will return false. An empty array returns true when every is used.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
/ / Array. The prototype. The find () and Array. The prototype. FindIndex () a.findIndex(i= > i === undefined) / / 1 a.find(i= > i === undefined) //undefined Copy the code
Because find and findIndex are implemented using a for loop, unlike forEach, they traverse the “empty” element.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Array.prototype.includes() a.includes() // true b.includes() // true c.includes() // false a.includes(undefined) // true b.includes(undefined) // true Copy the code
Includes () is special and can be summarized as follows:
- When the array length is zero,
include
Must return false; - When the array is sparse and the length is not 0, and the input parameter is empty or undefined,
include
Must return true;
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Array.prototype.sort() var sortArr = [5.9.1] sortArr.sort() console.log(sortArr) // Output: [1, 5, 9, empty × 2] Copy the code
The sort method can sort properly and the sort method does not traverse empty elements, but the length of the array does not change after sort, which is consistent with the representation of map and the length of the array obtained by map.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Array.prototype.join() [undefined.undefined.1].join() // Output: ",,1" [5.9.1].join() // Output: "5,,9,,1" Copy the code
The “empty” element will still be retained.
- When the array length is zero,
Other methods on array. prototype show that sparse and dense arrays behave roughly the same.
conclusion
The Array created by new Array(len) is a sparse Array. In some Array methods, especially the iterating Array method, the sparse Array is often different from what we expect. If we do not understand it, it may cause problems, and the performance of sparse Array is not good in creating and accessing elements. So you should avoid using sparse arrays in your normal code. There is no need to worry about it, as long as we understand it, it is not terrible ~