Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Extension of the ES6 array


May 08, 2021 ES6


Table of contents


1. Extended operator

Meaning

扩展运算符 (spread) is three points (... ) It is like an inverse operation of the rest parameter, which converts an array into a sequence of parameters separated by commas.

  1. console.log(...[1, 2, 3])
  2. // 1 2 3
  3. console.log(1, ...[2, 3, 4], 5)
  4. // 1 2 3 4 5
  5. [...document.querySelectorAll('div')]
  6. // [<div>, <div>, <div>]

This operator is primarily used 函数调用

  1. function push(array, ...items) {
  2. array.push(...items);
  3. }
  4. function add(x, y) {
  5. return x + y;
  6. }
  7. const numbers = [4, 38];
  8. add(...numbers) // 42

In the code above, array.push (... i tems) and add (... n umbers) Both lines are calls to functions, and they both use extended operators. The operator changes an array into a sequence of parameters.

Extended operators can be used in conjunction with normal function parameters and are very flexible.

  1. function f(v, w, x, y, z) { }
  2. const args = [0, 1];
  3. f(-1, ...args, 2, ...[3]);

You can also place expressions after extended operators.

  1. const arr = [
  2. ...(x > 0 ? ['a'] : []),
  3. 'b',
  4. ];

If the extension operator is followed by an empty array, it has no effect.

  1. [...[], 1]
  2. // [1]

Note that the extension operator can only be placed in parentheses when a function is called, otherwise an error will be reported.

  1. (...[1, 2])
  2. // Uncaught SyntaxError: Unexpected number
  3. console.log((...[1, 2]))
  4. // Uncaught SyntaxError: Unexpected number
  5. console.log(...[1, 2])
  6. // 1 2

In the above three cases, the extension operator is placed in parentheses, but the first two cases report an error because the parenthesis in which the extension operator is located is not a function call.

The apply method that replaces the function

Because the extension operator can expand the array, the apply method is no longer needed to convert the array into a function argument.

  1. // ES5 的写法
  2. function f(x, y, z) {
  3. // ...
  4. }
  5. var args = [0, 1, 2];
  6. f.apply(null, args);
  7. // ES6的写法
  8. function f(x, y, z) {
  9. // ...
  10. }
  11. let args = [0, 1, 2];
  12. f(...args);

The following is a practical example of an extended operator replacing the apply method, applying the Math.max method to simplify the writing of the largest element of an array.

  1. // ES5 的写法
  2. Math.max.apply(null, [14, 3, 77])
  3. // ES6 的写法
  4. Math.max(...[14, 3, 77])
  5. // 等同于
  6. Math.max(14, 3, 77);

In the above code, since JavaScript does not provide a function for the largest element of the array, the Math.max function can only be used to convert the array into a sequence of parameters and then the maximum value. Once you have the extension operator, you can use Math .max directly.

Another example is to add an array to the tail of another array through the push function.

  1. // ES5的 写法
  2. var arr1 = [0, 1, 2];
  3. var arr2 = [3, 4, 5];
  4. Array.prototype.push.apply(arr1, arr2);
  5. // ES6 的写法
  6. let arr1 = [0, 1, 2];
  7. let arr2 = [3, 4, 5];
  8. arr1.push(...arr2);

In the ES5 writing of the above code, the parameters of the push method cannot be arrays, so the push method has to be used in an apply method. With the extension operator, you can pass the array directly into the push method.

Here's another example.

  1. // ES5
  2. new (Date.bind.apply(Date, [null, 2015, 1, 1]))
  3. // ES6
  4. new Date(...[2015, 1, 1]);

The application of the extension operator

(1) Copy the array

Arrays are composite data types that, if copied directly, simply copy pointers to the underlying data structure, rather than cloning a completely new array.

  1. const a1 = [1, 2];
  2. const a2 = a1;
  3. a2[0] = 2;
  4. a1 // [2, 2]

In the code above, a2 is not a clone of a1, but another pointer to the same data. Modifying a2 will directly cause a1 to change.

ES5 can only copy arrays in a worktable way.

  1. const a1 = [1, 2];
  2. const a2 = a1.concat();
  3. a2[0] = 2;
  4. a1 // [1, 2]

In the code above, a1 returns a clone of the original array, and modifying a2 does not affect a1.

Extended operators provide a simple way to copy arrays.

  1. const a1 = [1, 2];
  2. // 写法一
  3. const a2 = [...a1];
  4. // 写法二
  5. const [...a2] = a1;

The above two writings, a2 are clones of a1.

(2) Merge arrays

Extended operators provide a combination of numbers and a new way to write them.

  1. const arr1 = ['a', 'b'];
  2. const arr2 = ['c'];
  3. const arr3 = ['d', 'e'];
  4. // ES5 的合并数组
  5. arr1.concat(arr2, arr3);
  6. // [ 'a', 'b', 'c', 'd', 'e' ]
  7. // ES6 的合并数组
  8. [...arr1, ...arr2, ...arr3]
  9. // [ 'a', 'b', 'c', 'd', 'e' ]

However, both methods are shallow copies and need to be noted when using them.

  1. const a1 = [{ foo: 1 }];
  2. const a2 = [{ bar: 2 }];
  3. const a3 = a1.concat(a2);
  4. const a4 = [...a1, ...a2];
  5. a3[0] === a1[0] // true
  6. a4[0] === a1[0] // true

In the above code, a3 and a4 are new arrays that are combined in two different ways, but their members are references to the members of the original array, which is a shallow copy. If you modify the value to which the reference points, it is reflected synchronously to the new array.

(3) Combined with deconstructed assignment

Extension operators can be combined with deconstructed assignments to generate arrays.

  1. // ES5
  2. a = list[0], rest = list.slice(1)
  3. // ES6
  4. [a, ...rest] = list

Here are some other examples.

  1. const [first, ...rest] = [1, 2, 3, 4, 5];
  2. first // 1
  3. rest // [2, 3, 4, 5]
  4. const [first, ...rest] = [];
  5. first // undefined
  6. rest // []
  7. const [first, ...rest] = ["foo"];
  8. first // "foo"
  9. rest // []

If the extension operator is used for array assignment, it can only be placed at the last bit of the argument, otherwise an error will be reported.

  1. const [...butLast, last] = [1, 2, 3, 4, 5];
  2. // 报错
  3. const [first, ...middle, last] = [1, 2, 3, 4, 5];
  4. // 报错

(4) string

扩展运算符 also turn 字符串 into 数组

  1. [...'hello']
  2. // [ "h", "e", "l", "l", "o" ]

One important benefit of the above writing is the correct recognition of four bytes of Unicode characters.

  1. 'x\uD83D\uDE80y'.length // 4
  2. [...'x\uD83D\uDE80y'].length // 3

In the first writing of the code above, JavaScript recognizes four bytes of Unicode characters as two characters, which is not a problem with extension operators. Therefore, functions that correctly return string length can be written as below.

  1. function length(str) {
  2. return [...str].length;
  3. }
  4. length('x\uD83D\uDE80y') // 3

This is a problem for functions that involve operating four bytes of Unicode characters. Therefore, it is best to override them all with extension operators.

  1. let str = 'x\uD83D\uDE80y';
  2. str.split('').reverse().join('')
  3. // 'y\uDE80\uD83Dx'
  4. [...str].reverse().join('')
  5. // 'y\uD83D\uDE80x'

In the code above, the reverse operation of the string is incorrect without the extension operator.

(5) The object that implements the Iterator interface

Any object that defines the traverser interface (see chapter Iterator) can be converted to a real array with an extended operator.

  1. let nodeList = document.querySelectorAll('div');
  2. let array = [...nodeList];

In the above code, the querySelectorAll method returns a NodeList object. I t is not an array, but an array-like object. At this point, the extension operator can turn it into a real array because the NodeList object implements Iterator.

  1. javascript
  2. Number.prototype[Symbol.iterator] = function*() {
  3. let i = 0;
  4. let num = this.valueOf();
  5. while (i < num) {
  6. yield i++;
  7. }
  8. }
  9. console.log([...5]) // [0, 1, 2, 3, 4]

In the code above, the number object's traverser interface is defined first, and when the extension operator automatically turns 5 into a Number instance, the interface is called and a custom result is returned.

For array-like objects that do not have an Iterator interface deployed, the extension operator cannot turn it into a real array.

  1. javascript
  2. let arrayLike = {
  3. '0': 'a',
  4. '1': 'b',
  5. '2': 'c',
  6. length: 3
  7. };
  8. // TypeError: Cannot spread non-iterable object.
  9. let arr = [...arrayLike];

In the code above, arrayLike is an array-like object, but without the Iterator interface deployed, the extension operator will report an error. At this point, you can use the Array.from method instead to turn arrayLike into a real array.

(6) Map and Set structure, Generator function

The Iterator interface for the data structure is called inside the extension operator, so you can use the extension operator, such as the Map structure, whenever you have an object with an Iterator interface.

  1. let map = new Map([
  2. [1, 'one'],
  3. [2, 'two'],
  4. [3, 'three'],
  5. ]);
  6. let arr = [...map.keys()]; // [1, 2, 3]

After the Generator function runs, a traverser object is returned, so you can also use an extension operator.

  1. const go = function*(){
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. };
  6. [...go()] // [1, 2, 3]

In the above code, the variable go is a Generator function that returns a traverser object after execution, and by executing an extension operator on the traverser object, the resulting value of the internal traversal is converted into an array.

If you use an extension operator for objects that do not have an Iterator interface, an error will be reported.

  1. const obj = {a: 1, b: 2};
  2. let arr = [...obj]; // TypeError: Cannot spread non-iterable object

2. Array.from()

The Array.from method is used to turn two types of objects into real arrays: array-like objects and traversable objects, including the new data structure Set and Map in ES6.

Here's an array-like object, array.from turning it into a real array.

  1. let arrayLike = {
  2. '0': 'a',
  3. '1': 'b',
  4. '2': 'c',
  5. length: 3
  6. };
  7. // ES5的写法
  8. var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
  9. // ES6的写法
  10. let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

In practice, common array-like objects are the NodeList collection returned by the DOM operation, and the arguments object inside the function. Array.from can turn them into real arrays.

  1. javascript
  2. // NodeList对象
  3. let ps = document.querySelectorAll('p');
  4. Array.from(ps).filter(p => {
  5. return p.textContent.length > 100;
  6. });
  7. // arguments对象
  8. function foo() {
  9. var args = Array.from(arguments);
  10. // ...
  11. }

In the above code, the querySelectorAll method returns an array-like object that can be turned into a real array and then used by the filter method.

Array.from can convert an Array.from to an array as long as the data structure of the Iterator interface is deployed.

  1. Array.from('hello')
  2. // ['h', 'e', 'l', 'l', 'o']
  3. let namesSet = new Set(['a', 'b'])
  4. Array.from(namesSet) // ['a', 'b']

In the code above, both the string and the Set structure have an Iterator interface, so they can be converted into real arrays by Array.from.

If the argument is a real array, Array.from returns a new array exactly the same.

  1. Array.from([1, 2, 3])
  2. // [1, 2, 3]

It is worth reminding that the extension operator (... You can also convert some data structures into arrays.

  1. // arguments对象
  2. function foo() {
  3. const args = [...arguments];
  4. }
  5. // NodeList对象
  6. [...document.querySelectorAll('div')]

Behind the extension operator is the 遍历器接口 interface (Symbol.iterator), which cannot be converted if an object does not deploy the interface. T he Array.from method also supports array-like objects. T he so-called array-like objects have only one essential feature, namely, the length attribute. Therefore, any object with the length property can be converted to an array through the Array.from method, at which point the extension operator cannot be converted.

  1. Array.from({ length: 3 });
  2. // [ undefined, undefined, undefined ]

In the above code, Array.from returns an array of three members, each with a value of undefined. The extension operator cannot convert this object.

For browsers that have not yet deployed the method, you Array.prototype.slice method.

  1. const toArray = (() =>
  2. Array.from ? Array.from : obj => [].slice.call(obj)
  3. )();

Array.from can also accept a second argument, similar to the array's map method, to process each element and put the processed value into the returned array.

  1. Array.from(arrayLike, x => x * x);
  2. // 等同于
  3. Array.from(arrayLike).map(x => x * x);
  4. Array.from([1, 2, 3], (x) => x * x)
  5. // [1, 4, 9]

The following example is to take out the text content of a set of DOM nodes.

  1. let spans = document.querySelectorAll('span.name');
  2. // map()
  3. let names1 = Array.prototype.map.call(spans, s => s.textContent);
  4. // Array.from()
  5. let names2 = Array.from(spans, s => s.textContent)

The following example turns a member of an array with a Boolean value of false to 0.

  1. Array.from([1, , 2, , 3], (n) => n || 0)
  2. // [1, 0, 2, 0, 3]

Another example is the type of data returned.

  1. function typesOf () {
  2. return Array.from(arguments, value => typeof value)
  3. }
  4. typesOf(null, [], NaN)
  5. // ['object', 'object', 'number']

If the this keyword is used in the map function, a third argument from Array.from can also be passed in to bind this .

Array.from() converts various values into real arrays and also provides map functionality. This actually means that as long as you have an original data structure, you can process its values and then move to a canoned array structure, which in turn can use a large number of array methods.

  1. Array.from({ length: 2 }, () => 'jack')
  2. // ['jack', 'jack']

In the above code, array.from's first argument specifies the number of times the second parameter has been run. This feature makes the use of this method very flexible.

Another application of Array.from() is to convert the string into an array and return the length of the string. Because it handles a variety of Unicode characters correctly, javaScript can avoid counting Unicode characters larger than the suFFFF as two-character bugs.

  1. function countSymbols(string) {
  2. return Array.from(string).length;
  3. }

3. Array.of()

Array.of method is used to convert a set of values into an array.

  1. Array.of(3, 11, 8) // [3,11,8]
  2. Array.of(3) // [3]
  3. Array.of(3).length // 1

The main purpose of this method is to 数组构造函数 array constructor Array(). Because of the difference in the number of parameters, the behavior of Array() is different.

  1. Array() // []
  2. Array(3) // [, , ,]
  3. Array(3, 11, 8) // [3, 11, 8]

In the above code, the Array method returns different results when it does not have parameters, one argument, or three parameters. A rray() returns a new array of parameters only if there are not less than 2 arguments. When there is only one number of parameters, it is actually the length of the specified array.

Array.of can basically be used as an alternative to Array() new Array() and there is no overload due to different parameters. It behaves very uniformly.

  1. Array.of() // []
  2. Array.of(undefined) // [undefined]
  3. Array.of(1) // [1]
  4. Array.of(1, 2) // [1, 2]

Array.of always returns an array of parameter values. If there are no arguments, an empty array is returned.

The Array.of method can be simulated with the following code.

  1. function ArrayOf(){
  2. return [].slice.call(arguments);
  3. }

4. CopyWithin() for array instances

The copyWithin() members of a specified location to a different location (overwriting the original members) within the current array, and then returns the current array. That is, using this method, the current array is modified.

  1. Array.prototype.copyWithin(target, start = 0, end = this.length)

It accepts three parameters.

  • Target (required): Replace the data from this location. If it is negative, it represents the countdown.
  • Start (optional): Read the data from this location, which defaults to 0. If it is negative, it means that the calculation starts at the end.
  • End (optional): Stop reading data before going to that location, which is equal to the length of the array by default. If it is negative, it means that the calculation starts at the end.

All three parameters should be numeric and, if not, automatically converted to numeric values.

  1. [1, 2, 3, 4, 5].copyWithin(0, 3)
  2. // [4, 5, 3, 4, 5]

The above code indicates that the members (4 and 5) from bit 3 until the end of the array are copied to the position starting at bit 0, and the results cover the original 1 and 2.

Here are more examples.

  1. // 将3号位复制到0号位
  2. [1, 2, 3, 4, 5].copyWithin(0, 3, 4)
  3. // [4, 2, 3, 4, 5]
  4. // -2相当于3号位,-1相当于4号位
  5. [1, 2, 3, 4, 5].copyWithin(0, -2, -1)
  6. // [4, 2, 3, 4, 5]
  7. // 将3号位复制到0号位
  8. [].copyWithin.call({length: 5, 3: 1}, 0, 3)
  9. // {0: 1, 3: 1, length: 5}
  10. // 将2号位到数组结束,复制到0号位
  11. let i32a = new Int32Array([1, 2, 3, 4, 5]);
  12. i32a.copyWithin(0, 2);
  13. // Int32Array [3, 4, 5, 4, 5]
  14. // 对于没有部署 TypedArray 的 copyWithin 方法的平台
  15. // 需要采用下面的写法
  16. [].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
  17. // Int32Array [4, 2, 3, 4, 5]

5. Find() and findIndex() for array instances

The find method of find instance to find the first eligible array member. I ts argument is a callback function, and all array members execute the callback function in turn until they find the first member that returns a value of true, and then returns that member. If there are no eligible members, undefined is returned.

  1. [1, 4, -5, 10].find((n) => n < 0)
  2. // -5

The above code finds the first member in the array that is less than 0.

  1. [1, 5, 10, 15].find(function(value, index, arr) {
  2. return value > 9;
  3. }) // 10

In the above code, the callback function of the find method can accept three parameters, followed by the current value, the current position, and the original array.

The findIndex is very similar to the find method, returning the position of the first eligible array member and -1 if all members do not meet the criteria.

  1. [1, 5, 10, 15].findIndex(function(value, index, arr) {
  2. return value > 9;
  3. }) // 2

Both methods accept the second argument to bind the this object of the callback function.

  1. function f(v){
  2. return v > this.age;
  3. }
  4. let person = {name: 'John', age: 20};
  5. [10, 12, 26, 15].find(f, person); // 26

In the above code, the find function receives the second argument person object, and the this object in the callback function points to the person object.

In addition, both methods can find NaN, which makes up for the lack of indexOf methods for arrays.

  1. [NaN].indexOf(NaN)
  2. // -1
  3. [NaN].findIndex(y => Object.is(NaN, y))
  4. // 0

In the above code, the indexOf method does not recognize the NaN members of the array, but the findIndex method can do so Object.is method.

6. Fill() for array instances

The fill method fills an array with a given value.

  1. ['a', 'b', 'c'].fill(7)
  2. // [7, 7, 7]
  3. new Array(3).fill(7)
  4. // [7, 7, 7]

The above code indicates that the fill method is convenient for initializing empty arrays. Elements already in the array are erased.

The fill method can also accept the second and third parameters, which specify the start and end positions of the fill.

  1. ['a', 'b', 'c'].fill(7, 1, 2)
  2. // ['a', 7, 'c']

The above code indicates that the fill method starts at bit 1, fills the original array with 7, and ends before bit 2.

Note that if the type of population is an object, it is the object that is assigned the same memory address, not the deep copy object.

  1. let arr = new Array(3).fill({name: "Mike"});
  2. arr[0].name = "Ben";
  3. arr
  4. // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
  5. let arr = new Array(3).fill([]);
  6. arr[0].push(5);
  7. arr
  8. // [[5], [5], [5]]

7. Entries (), keys() and values () for array instances

ES6 provides three new entries() keys() values() for traversing arrays. T hey all return a 遍历器对象 (see chapter Iterator for details), which can be used for... The only difference in the loop is that keys() is traversal of key names, values() are traversals of key values, entries() are traversals of key value pairs.

  1. for (let index of ['a', 'b'].keys()) {
  2. console.log(index);
  3. }
  4. // 0
  5. // 1
  6. for (let elem of ['a', 'b'].values()) {
  7. console.log(elem);
  8. }
  9. // 'a'
  10. // 'b'
  11. for (let [index, elem] of ['a', 'b'].entries()) {
  12. console.log(index, elem);
  13. }
  14. // 0 "a"
  15. // 1 "b"

If you don't use for... Of loop, you can manually call the next method of the traverser object for traversal.

  1. let letter = ['a', 'b', 'c'];
  2. let entries = letter.entries();
  3. console.log(entries.next().value); // [0, 'a']
  4. console.log(entries.next().value); // [1, 'b']
  5. console.log(entries.next().value); // [2, 'c']

8. Includes() for array instances

Array.prototype.includes a Boolean value that indicates whether an array contains a given value, similar to the includes method of a string. ES2016 introduces this approach.

  1. [1, 2, 3].includes(2) // true
  2. [1, 2, 3].includes(4) // false
  3. [1, 2, NaN].includes(NaN) // true

The second parameter of the method represents the starting position of the search, which defaults to 0. If the second argument is negative, it represents the position of the countdown, and if it is greater than the length of the array (for example, the second argument is -4 but the array length is 3), it is reset to start at 0.

  1. [1, 2, 3].includes(3, 3); // false
  2. [1, 2, 3].includes(3, -1); // true

Without this method, we usually use the indexOf method of the array to check if a value is included.

  1. if (arr.indexOf(el) !== -1) {
  2. // ...
  3. }

The indexOf method has two disadvantages, one is not semantic enough, its meaning is to find the first appearance of the parameter value, so to compare whether it is not equal to -1, the expression is not intuitive enough. Second, it uses a strict equal operator internally to make a judgment, which can lead to a misjudgment of NaN.

  1. [NaN].indexOf(NaN)
  2. // -1

Includes uses a different algorithm of judgment, and there is no such problem.

  1. [NaN].includes(NaN)
  2. // true

The following code checks whether the current environment supports this approach and, if not, deploys a simple alternative version.

  1. const contains = (() =>
  2. Array.prototype.includes
  3. ? (arr, value) => arr.includes(value)
  4. : (arr, value) => arr.some(el => el === value)
  5. )();
  6. contains(['foo', 'bar'], 'baz'); // => false

In addition, Map and Set data structures have a has that requires attention to distinguish it from includes.

  • The has method of the Map structure, which is used to find key names such as Map.prototype.has (key), WeakMap.prototype.has (key), and Reflect.has (target, propertyKey).
  • The has method of the Set structure, which is used to find values such as Set.prototype.has (value), WeakSet.prototype.has (value).

9. Flat(), flatMap() for array instances

The members of an array are sometimes arrays, Array.prototype.flat() is used to “拉平” nested arrays into one-dimensional arrays. The method returns a new array with no effect on the original data.

  1. [1, 2, [3, 4]].flat()
  2. // [1, 2, 3, 4]

In the code above, the members of the original array have an array inside, and flat() removes the members of the sub-array and adds them in their original position.

flat() “拉平” and if you want to "flatten" a nested array of multiple layers, you can write the parameters of the flat() method as an integer, indicating the number of layers you want to level, defaulting to 1.

  1. [1, 2, [3, [4, 5]]].flat()
  2. // [1, 2, 3, [4, 5]]
  3. [1, 2, [3, [4, 5]]].flat(2)
  4. // [1, 2, 3, 4, 5]

In the code above, the parameter flat() is 2, which means that you want to "flatten" the nested array of two layers.

If you turn into a one-dimensional array no matter how many layers are nested, you can use the Infinity keyword as an argument.

  1. [1, [2, [3]]].flat(Infinity)
  2. // [1, 2, 3]

If the original array is empty, the flat() method skips the empty space.

  1. [1, 2, , 4, 5].flat()
  2. // [1, 2, 4, 5]

flatMap() executes a function (equivalent to array.prototype.map() on each member of the original array, and then flat() on an array of returned values. The method returns a new array without changing the original array.

  1. // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
  2. [2, 3, 4].flatMap((x) => [x, x * 2])
  3. // [2, 4, 3, 6, 4, 8]

FlatMap() can only expand one level of array.

  1. // 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
  2. [1, 2, 3, 4].flatMap(x => [[x * 2]])
  3. // [[2], [4], [6], [8]]

In the above code, the traversal function returns a two-tiered array, but by default only one layer can be expanded, so flatMap() returns a nested array.

The parameter of the flatMap() method is a 遍历函数 function that can accept three parameters, namely, the current array member, the position of the current array member (starting from zero), and the original array.

  1. arr.flatMap(function callback(currentValue[, index[, array]]) {
  2. // ...
  3. }[, thisArg])

The flatMap() method can also have a second argument to bind the this inside the traversal function.

10. Empty spaces for the array

An empty bit of an array means that there is no value at one location of the array. For example, array constructors return arrays that are empty.

  1. Array(3) // [, , ,]

In the code above, Array(3) returns an array with 3 empty spaces.

Note that the empty space is not undefined, and the value of a location is equal to the undefined and is still valued. Empty spaces are free of any values, as the in operator illustrates.

  1. 0 in [undefined, undefined, undefined] // true
  2. 0 in [, , ,] // false

The code above indicates that the no. 0 position of the first array has a value, and the no 0 position of the second array has no value.

ES5's handling of empty spaces is already inconsistent and in most cases empty spaces are ignored.

  • ForEach(), filter(), reduce(), every () and some() all skip empty seats.
  • map() skips the empty space, but retains this value
  • join() and toString() treat empty spaces as undefined, while undefined and null are processed as empty strings.

  1. // forEach方法
  2. [,'a'].forEach((x,i) => console.log(i)); // 1
  3. // filter方法
  4. ['a',,'b'].filter(x => true) // ['a','b']
  5. // every方法
  6. [,'a'].every(x => x==='a') // true
  7. // reduce方法
  8. [1,,2].reduce((x,y) => x+y) // 3
  9. // some方法
  10. [,'a'].some(x => x !== 'a') // false
  11. // map方法
  12. [,'a'].map(x => 1) // [,1]
  13. // join方法
  14. [,'a',undefined,null].join('#') // "#a##"
  15. // toString方法
  16. [,'a',undefined,null].toString() // ",a,,"

ES6 explicitly converts empty spaces to undefined.

Array.from method turns the empty spaces of the array into undefined, that is, the method does not ignore the empty spaces.

  1. Array.from(['a',,'b'])
  2. // [ "a", undefined, "b" ]

Extension operator (... The empty space is also converted to undefined.

  1. [...['a',,'b']]
  2. // [ "a", undefined, "b" ]

CopyWithin() is copied with empty bits.

  1. [,'a','b',,].copyWithin(2,0) // [,"a",,"a"]

fill() treats empty spaces as normal array positions.

  1. new Array(3).fill('a') // ["a","a","a"]

for... The of loop also traverses the empty space.

  1. let arr = [, ,];
  2. for (let i of arr) {
  3. console.log(1);
  4. }
  5. // 1
  6. // 1

In the code above, array arr has two empty spaces, for... o f did not ignore them. If you change to the map method for traversal, the empty spaces are skipped.

entries(), keys(), values(), find() and findIndex() process empty spaces as undefined.

  1. // entries()
  2. [...[,'a'].entries()] // [[0,undefined], [1,"a"]]
  3. // keys()
  4. [...[,'a'].keys()] // [0,1]
  5. // values()
  6. [...[,'a'].values()] // [undefined,"a"]
  7. // find()
  8. [,'a'].find(x => true) // undefined
  9. // findIndex()
  10. [,'a'].findIndex(x => true) // 0

Because the processing rules for empty spaces are very different, it is recommended to avoid empty spaces.

11. Sort stability of Array.prototype.sort().

排序稳定性 is an important attribute of the sorting algorithm, which refers to items with the same sort keywords, and the order before and after sorting remains the same.

  1. const arr = [
  2. 'peach',
  3. 'straw',
  4. 'apple',
  5. 'spork'
  6. ];
  7. const stableSorting = (s1, s2) => {
  8. if (s1[0] < s2[0]) return -1;
  9. return 1;
  10. };
  11. arr.sort(stableSorting)
  12. // ["apple", "peach", "straw", "spork"]

The above code sorts the array arr by initials. In the sort results, straw is in front of the spork, consistent with the original order, so the sorting algorithm stableSorting is stable sorting.

  1. const unstableSorting = (s1, s2) => {
  2. if (s1[0] <= s2[0]) return -1;
  3. return 1;
  4. };
  5. arr.sort(unstableSorting)
  6. // ["apple", "peach", "spork", "straw"]

In the code above, the sort result is that spork is in front of straw, contrary to the original order, so the sorting algorithm unstableSorting is unstable.

Among the common sorting algorithms, 插入排序 合并排序 bubbling sorting, etc. are stable, 冒泡排序 堆排序 快速排序 etc. are unstable. T he main disadvantage of unstable sorting is that there can be problems with multiple sorting. S uppose you have a list of first and last names that require sorting by "Last name as the primary keyword and first name as the secondary keyword." D evelopers may sort by first name and then by last name. I f the sorting algorithm is stable, you can achieve the sorting effect of First Last Name, Then First Name. If it's unstable, it's not.

Earlier ECMAScript did not specify whether array.prototype.sort()'s default sorting algorithm was stable, leaving it up to the browser to decide for itself, which led to some implementations being unstable. arly states that the default sorting algorithm for Array.prototype.sort() must be stable. This rule has been done, and now the default sorting algorithms for each of JavaScript's major implementations are stable.