扩展运算符
含义
扩展运算符(spread)是三个点(...),可以当作rest参数的逆运算,将数组转为用逗号分隔的参数序列
rest是把传入的参数组合成一个数组
扩展运算符是把数组分解成参数
console.log(...[1,2,3])
//1,2,3
console.log(1,...[2,3,4],5)
//1,2,3,4,5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
- 该运算符主要用于函数的调用,将一个数组,变为参数序列
function push(array,...items){
array.push(...items)
}
function add(x,y){
return x+y
}
const numbers = [4,38]
add(...numbers) //42
- ...可以与正常的参数结合使用
function f(v,w,x,y,z){
console.log(v,w,x,y,z) //-1 0 1 2 3
}
const args = [0,1]
f(-1,...args,2,...[3]);
- ...后面还可以放置表达式
var x = 1
const arr = [
...(x>0?['a']:[]),
'b',
]
console.log(arr) //['a','b']
- 如果扩展运算符后面是一个空数组,则不产生任何效果。
[...[], 1]
// [1]
- 只有函数调用时,扩展运算符才可以放在圆括号中
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1, 2])
// 1 2
上面三种情况,扩展运算符都放在圆括号里面,
但是前两种情况会报错,因为扩展运算符所在的括号不是函数调用。
替代apply方法
es5:
function f(x,y,z){
console.log(x,y,z) // 0 1 2
}
var args = [0,1,2];
f.apply(null,args)
因为f接收的参数不能为数组,为了方便,可以用apply方法来实现用数组的参数来传递,
这是很多时候运用的一个小技巧罢了。
而apply方法第一个参数,是要替代的对象。没有要替代的,用null,也是很自然的
es6:
function f(x,y,z){
console.log(x,y,z) // 0 1 2
}
var args = [0,1,2]
f(...args)
- 应用:Math.max
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
- 将一个数组添加到另一个数组的尾部
es5:
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1,arr2)
console.log(arr1)//[0,1,2,3,4,5]
push方法的参数不能是数组,所以只好通过apply方法变通使用push方法
es6:
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
console.log(arr1)//[0,1,2,3,4,5]
- 另一个例子
// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);
扩展运算符的应用
复制数组
数组是复合数据类型,所以直接复制的话是复制指向底层数据结构的指针,不是一个新数组
var a1 = [1,2]
var a2 = a1
a2[0]= 2
a1 //[2,2]
所以es5的变通方法:
var a1 = [1, 2];
var a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
concat:用于连接2个或多个数组,不会改变现有数组,会返回被连接的数组的副本
如果参数是数组,那么添加的是数组中的元素
es6:
var a1 = [1, 2];
var a2 = [...a1]
或者
var [...a2] = a1
合并数组
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
a3和a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。
如果修改了引用指向的值,会同步反映到新数组。
var a1 = [{foo:1}]
var a2 = [{bar:2}]
var a3 = a1.concat(a2)
var a4 = [...a1,...a2]
a3[0] === a1[0] // true
a4[0] === a1[0] // true
与解构赋值结合
用于生成数组
// ES5
a = list[0],rest = list.slice(1)
// ES6
[a,...rest] = list
rest // list[1]
slice(start,end):截取数组中选定的元素并返回,
start为负,从数组尾部开始算起的位置
end,可选,不写则包含从 start 到数组结束的所有元素
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错
字符串
将字符串转为真正的数组
[...'hello']
// [ "h", "e", "l", "l", "o" ]
实现了Iterator接口的对象
)
)
Map,Set结构,Generator函数
let map = new Map([
['1','one'],
['2','two'],
['3','three'],
]);
let arr = [...map.keys()]
- Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
const go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3]
- 如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。
const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object
Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike
想要转换成数组的伪数组对象或可迭代对象。
mapFn 可选
如果指定了该参数,新数组中的每个元素会执行该回调函数。
thisArg 可选
可选参数,执行回调函数 mapFn 时 this 对象。
* 用于将类似数组的对象和可遍历的对象转为真正的数组
let arrayLike = {
'0':'a',
"1":'b',
"2":'c',
length:3
}
es5:
var arr1 = [].slice.call(arrayLike) //['a','b','c']
es6:
let arr2 = Array.from(arrayLike) //['a','b','c']
- 实际应用中,Array.from将常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象转为数组
let ps = document.querySelectorAll('p')
Array.from(ps).filter(p=>{
return p.textContent.length >100
})
function foo(){
var args = Array.from(arguments)
}
- 只要是部署了 Iterator 接口的数据结构,Array.from都能将其转为数组。
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
- 如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组
var arr1 = [1,2,3]
var arr2 = Array.from(arr1)
arr2 === arr1 //false
- ...也可以将某些数据结构转为数组
function foo(){
var args = [...arguments]
}
[...document.querySelectorAll('div')]
扩展运算符背后调用的是遍历器接口,如果这个对象没有部署这个接口,就无法转换
Array.from可以转化有length属性的类似数组的对象,而扩展运算符就无法转换
Array.from({length:3})
// [ undefined, undefined, undefined ]
上面代码中,Array.from返回了一个具有三个成员的数组,每个位置的值都是undefined。
扩展运算符转换不了这个对象。
- Array.from的第二个参数,类似map,对每一个元素进行处理
Array.from(arr,x=>x*x)
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
下面的例子是取出一组 DOM 节点的文本内容
let spans = document.querySelectorAll('span.name');
let names1 = Array.prototype.map.call(spans,s=>s.textContent);
.map():返回一个新数组,原始数组调用函数处理后的值
let names2 = Array.from(spans,s=>s.textContent)
将数组中布尔值为false的成员转为0
Array.from([1,2,3],n=>n||0)
// [1, 0, 2, 0, 3]
另一个例子是返回各种数据的类型。
function typesOf () {
return Array.from(arguments, value => typeof value)
}
typesOf(null, [], NaN)
// ['object', 'object', 'number']
Array.from的第一个参数指定了第二个参数运行的次数。这种特性可以让该方法的用法变得非常灵活。
Array.from({length:2},()=>'jack')
// ['jack', 'jack']
将字符串转为数组,返回字符串的长度
function countSymbols(string){
return Array.from(String).length
}
Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
Array.of()
将一组值转换为数组,主要是弥补Array()的不足
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
Array方法没有参数、一个参数、三个参数时,返回结果都不一样。
只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。
参数个数只有一个时,实际上是指定数组的长度。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
let arr = Array.of(1, 2, 3, 4)
console.log(arr) // [1, 2, 3, 4]
console.log(arr.length) // 4
Array.of可以替代Array()或者new Array()
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
Array.of方法可以用下面的代码模拟实现
function ArrayOf(){
return [].slice.call(arguments)
}
数组实例的copyWithin()
在当前数组中,将指定位置的成员复制到其他位置(覆盖原有成员),然后返回当前被改变的数组
Array.prototype.copyWithin(target,start,end)
target:从这个位置开始替换数据,如果数值为负,表示倒数
start(可选):从这个位置开始读取数据,默认为0,如果数值为负,表示从末尾开始计算
end(可选):到该位置前停止读取数据,默认为数组的长度,如果数值为负,表示从末尾开始计算
例子:
[1,2,3,4,5].copyWithin(0,3) //[4,5,3,4,5]
从0开始替换,替换数字是第3位到数组结束,所以是4,5,替换掉了1,2
[1,2,3,4,5].copyWithin(0,3,4) //[4,2,3,4,5]
从0开始替换,替换数字是第3位到第4位结束,所以是4,替换掉了1
[1,2,3,4,5].copeWithin(0,-2,-1) //[4, 2, 3, 4, 5]
![Int32Array和call的问题](9-数组的扩展_files/4.jpg)
find()和findIndex()
find:返回找出的第一个符合条件(返回值为true)的数组成员,如果没有,返回undefined
findIndex:返回第一个符合条件的数组成员的位置,如果没有,返回-1
find(callback(value,index,arr),thisArg)
findIndex(callback(value,index,arr),thisArg)
value:当前的值
index:当前的位置
arr:原数组
thisArg:绑定回调函数的this对象
例子:
[1, 4, -5, 10].find((n) => n < 0) // -5
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
[1,5,10,15].findIndex(function(value,index,arr){
return value >9
}) //2
function f(v){
return v>this.age //this指向person
}
let person = {name:'John',age:20};
[10,12,26,15].find(f,person); //26
另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
上面代码中,indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。
Object.is():判断2个值是否是同一个值,不进行强制转换,需要满足以下条件:
* 都是undefined
* 都是null
* 都是true或false
* 都是长度相同的字符串且字符排列顺序相同
* 都是相同对象(意味着同一个引用)
* 都是数字,都是 +0,都是 -0,都是 NaN,或都是非零而且非 NaN 且为同一个值
注意:+0 != -0
es5替换:
if (!Object.is) {
Object.is = function(x, y) {
// SameValue algorithm
if (x === y) {
return x !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
};
}
数组实例的fill()
使用指定值,填充(修改)一个数组,其实就是用默认内容初始化数组
fill(value,start,end)
value:填充值
start:填充起始位置
end:填充结束位置,可以省略,实际结束位置是end-1
例子:
['a','b','c'].fill(7) //[7,7,7]
new Array.fill(7) //[7,7,7]
如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
let arr = new Array(3).fill({name: "Mike"},0,1);
arr[0].name = "Ben";
arr // [{name: "Ben"},empty*2] =====>length:3
let arr = new Array(3).fill([]);
arr[0].push(5);
arr // [[5], [5], [5]]
数组实例的entries(),keys(),values()
用于遍历数组,返回一个遍历器对象,可以用for...of循环进行遍历
enteries():键值对的遍历
keys():键名的遍历
values():键值的遍历
for(let index of ['a','b'].keys()){
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
数组实例的includes()
- 检查数组中是否包含给定的值
includes(value,start)
value:查找的內容
strat:搜索的起始位置,默认为0,如果负数,从后开始,如果大于数组长度,从头开始
- 例子
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
相较于indexOf(),不需要比较是否等于-1,NaN也可以检测
[NaN].indexOf(NaN)
// -1
[NaN].includes(NaN)
// true
es5:
var contains = (()=>
Array.prototype.includes
? (arr, value) => arr.includes(value)
: (arr, value) => arr.some(el => el === value)
)()
contains(['foo', 'bar'], 'baz'); // => false
数组实例的flat(),flatMap()
flat
- flat():将嵌套数组变成一维数组,取出数组,放在原来的位置,返回新数组
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
- flat(n):默认拉平一层,为1,如果想要“拉平”多层,将flat()的参数写成一个想要拉平层数的整数,
如果不确定层数,可以用Infinity
[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
- 如果原数组有空位,flat()方法会跳过空位。
[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]
flatMap()
对arr的每一个成员执行函数,然后对返回值组成的数组执行flat(),返回新数组,只能展开一层
arr.flatMap(function callback(currentValue[, index[, array]]) {
// ...
}[, thisArg])
callback:回调函数:三个参数:
currentValue:当前参数
index:当前参数的位置
array:原数组
thisArg:绑定回调函数里的this
[2,3,4].flatMap((x=>[x,x*2]))
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
// [2, 4, 3, 6, 4, 8]
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
// [[2], [4], [6], [8]]
数组的空位
数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。
Array(3) // [, , ,] 返回一个具有 3 个空位的数组
空位不等于undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点
第一个数组的 0 号位置是有值的,第二个数组的 0 号位置没有值:
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
ES5和ES6对于空位的处理
ES5:
forEach(),filter(),reduce(),every(),some(),会跳过空位
map(),跳过空位,但保留值
join(),toString(),将空位视为undefined,undefined和null被处理成空字符串
)
ES6:将空位转为undefined
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
[...['a',,'b']]
// [ "a", undefined, "b" ]
[,'a','b',,].copyWithin(2,0)
// [,"a",,"a"]
new Array(3).fill('a')
// ["a","a","a"]
let arr = [, ,];
for (let i of arr) {
console.log(1);
}
// 1
// 1
// 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
Array.prototype.sort的排序稳定性
- 排序稳定
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是不稳定的。
常见的排序算法之中,插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。
不稳定排序的主要缺点是,多重排序时可能会产生问题。
假设有一个姓和名的列表,要求按照“姓氏为主要关键字,名字为次要关键字”进行排序。
开发者可能会先按名字排序,再按姓氏进行排序。如果排序算法是稳定的,这样就可以达到“先姓氏,后名字”的排序效果。如果是不稳定的,就不行。
ES2019 明确规定,Array.prototype.sort()的默认排序算法必须稳定。
这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。