ES6 入门教程
ECMAScript 6 入门
作者:阮一峰
本文仅用于学习记录,不存在任何商业用途,如侵删
文章目录
- ES6 入门教程
- 9 数组的扩展
- 9.12 实例方法:group(),groupToMap()
- 9.13 数组的空位
- 9.14 Array.prototype.sort() 的排序稳定性
9 数组的扩展
9.12 实例方法:group(),groupToMap()
数组成员分组是一个常见需求,比如 SQL 有GROUP BY
子句和函数式编程有 MapReduce 方法。
现在有一个提案,为 JavaScript 新增了数组实例方法group()
和groupToMap()
,它们可以根据分组函数的运行结果,将数组成员分组。
group()
的参数是一个分组函数,原数组的每个成员都会依次执行这个函数,确定自己是哪一个组。
const array = [1, 2, 3, 4, 5];
array.group((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// { odd: [1, 3, 5], even: [2, 4] }
group()
的分组函数可以接受三个参数,依次是数组的当前成员、该成员的位置序号、原数组(上例是num
、index
和array
)。
分组函数的返回值应该是字符串(或者可以自动转为字符串),以作为分组后的组名。
group()
的返回值是一个对象,该对象的键名就是每一组的组名,即分组函数返回的每一个字符串(上例是even
和odd
);该对象的键值是一个数组,包括所有产生当前键名的原数组成员。
下面是另一个例子。
[6.1, 4.2, 6.3].groupBy(Math.floor)
// { '4': [4.2], '6': [6.1, 6.3] }
上面示例中,Math.floor
作为分组函数,对原数组进行分组。它的返回值原本是数值,这时会自动转为字符串,作为分组的组名。
原数组的成员根据分组函数的运行结果,进入对应的组。
group()
还可以接受一个对象,作为第二个参数。该对象会绑定分组函数(第一个参数)里面的this
,不过如果分组函数是一个箭头函数,该对象无效,因为箭头函数内部的this
是固化的。
groupToMap()
的作用和用法与group()
完全一致,唯一的区别是返回值是一个 Map 结构,而不是对象。Map 结构的键名可以是各种值,所以不管分组函数返回什么值,都会直接作为组名(Map 结构的键名),不会强制转为字符串。这对于分组函数返回值是对象的情况,尤其有用。
const array = [1, 2, 3, 4, 5];
const odd = { odd: true };
const even = { even: true };
array.groupToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
// Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
上面示例返回的是一个 Map 结构,它的键名就是分组函数返回的两个对象odd
和even
。
总之,按照字符串分组就使用group()
,按照对象分组就使用groupToMap()
。
9.13 数组的空位
数组的空位指的是,数组的某一个位置没有任何值,比如Array()
构造函数返回的数组都是空位。
Array(3) // [, , ,]
上面代码中,Array(3)
返回一个具有 3 个空位的数组。
注意,空位不是undefined
,某一个位置的值等于undefined
,依然是有值的。
空位是没有任何值,in
运算符可以说明这一点。
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
上面代码说明,第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值。
ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
-
forEach()
,filter()
,reduce()
,every()
和some()
都会跳过空位。 -
map()
会跳过空位,但会保留这个值 -
join()
和toString()
会将空位视为undefined
,而undefined
和null
会被处理成空字符串。
// forEach方法
[,'a'].forEach((x,i) => console.log(i)); // 1
// filter方法
['a',,'b'].filter(x => true) // ['a','b']
// every方法
[,'a'].every(x => x==='a') // true
// reduce方法
[1,,2].reduce((x,y) => x+y) // 3
// some方法
[,'a'].some(x => x !== 'a') // false
// map方法
[,'a'].map(x => 1) // [,1]
// join方法
[,'a',undefined,null].join('#') // "#a##"
// toString方法
[,'a',undefined,null].toString() // ",a,,"
ES6 则是明确将空位转为undefined
。
Array.from()
方法会将数组的空位,转为undefined
,也就是说,这个方法不会忽略空位。
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
扩展运算符(...
)也会将空位转为undefined
。
[...['a',,'b']]
// [ "a", undefined, "b" ]
copyWithin()
会连空位一起拷贝。
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
fill()
会将空位视为正常的数组位置。
new Array(3).fill('a') // ["a","a","a"]
for...of
循环也会遍历空位。
let arr = [, ,];
for (let i of arr) {
console.log(1);
}
// 1
// 1
上面代码中,数组arr
有两个空位,for...of
并没有忽略它们。
如果改成map()
方法遍历,空位是会跳过的。
entries()
、keys()
、values()
、find()
和findIndex()
会将空位处理成undefined
。
// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]
// keys()
[...[,'a'].keys()] // [0,1]
// values()
[...[,'a'].values()] // [undefined,"a"]
// find()
[,'a'].find(x => true) // undefined
// findIndex()
[,'a'].findIndex(x => true) // 0
由于空位的处理规则非常不统一,所以建议避免出现空位。
9.14 Array.prototype.sort() 的排序稳定性
排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变。
const arr = [
'peach',
'straw',
'apple',
'spork'
];
const stableSorting = (s1, s2) => {
if (s1[0] < s2[0]) return -1;
return 1;
};
arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]
上面代码对数组arr
按照首字母进行排序。
排序结果中,straw
在spork
的前面,跟原始顺序一致,所以排序算法stableSorting
是稳定排序。
const unstableSorting = (s1, s2) => {
if (s1[0] <= s2[0]) return -1;
return 1;
};
arr.sort(unstableSorting)
// ["apple", "peach", "spork", "straw"]
上面代码中,排序结果是spork
在straw
前面,跟原始顺序相反,所以排序算法unstableSorting
是不稳定的。
常见的排序算法之中,插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。
不稳定排序的主要缺点是,多重排序时可能会产生问题。
假设有一个姓和名的列表,要求按照“姓氏为主要关键字,名字为次要关键字”进行排序。开发者可能会先按名字排序,再按姓氏进行排序。如果排序算法是稳定的,这样就可以达到“先姓氏,后名字”的排序效果。如果是不稳定的,就不行。
早先的 ECMAScript 没有规定,Array.prototype.sort()
的默认排序算法是否稳定,留给浏览器自己决定,这导致某些实现是不稳定的。
ES2019 明确规定,Array.prototype.sort()
的默认排序算法必须稳定。
这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。