ES6扩展操作符和剩余操作符区别和应用
扩展运算符写法是三个点…,写法虽然跟剩余操作符一致,都是…,但是作用可以认为是相反的。
扩展运算符
扩展运算符的核心就是2个字:打散。
剩余操作符的核心就是2个字:打包。
剩余操作符和扩展运算符在赋值方面的对比:
剩余操作符是:表示剩下的打包
,通常是只可能放在变量名
前面;一定有赋值或者传值操作:
1
2
let [a, ...b] = [1,2,3,4,5];
b; // [2,3,4,5]
扩展运算符是:表示把打包好的打散
,可能放在变量名
前面,也可能放在值
的前面;可能有赋值操作,也可能没有赋值操作:
1
2
let a = [1, ...[2,3,4]]; // 注意,既然是先打散然后赋值给一个变量,就需要用[]包起来形成数组
a; // [1, 2, 3, 4]
1
console.log(1, ...[2,3,4]); // 1, 2, 3, 4
剩余操作符跟扩展运算符的在函数方面的应用对比:
- 剩余操作符举例:
1
2
3
4
5
function ff (...a) {
return a[0] + '-' + a[1] + '-' + a[2];
}
ff(1,3,5); // '1-3-5'
- 扩展运算符举例:
1
2
3
4
5
6
7
function ff (a,b,c) {
return a + '-' + b + '-' + c;
}
let x = [1,3,5];
ff(...x); // '1-3-5'
扩展运算符可以跟表达式,将表达式的运算结果打散。
1
console.log(...('abcd'.split(''))); // a, b, c, d
本质理解
其实ES6之所以把这两个关键字设成一致的写法,是因为它们本质是一样,都是解构赋值。
我们复习一下解构赋值,下面用的是剩余操作符:
1
let [a, b, ...c] = [1,2,3,4,5];
JS引擎会拿元素依次为变量赋值,到c变量的时候,会一次性的把[3,4,5]赋值给c,虽然直观的感受是c变量打包了3个元素,但是其实等于是…把c打散成了3个变量位置,然后一对一赋值,最后再合成一个数组。
再看运用扩展运算符的解构赋值:
1
2
let [a, b, c] = [...[1,2,3]];
c; // 3
其实…也是先打散数组,然后再一对一赋值。
最后看一个朴素的解构赋值的栗子:
1
2
3
4
5
let [...a] = [...[1,2,3]]; // 右边打散成元素,左边又给打包起来
a; // [1, 2, 3]
// 忽略掉外面的数组括号,忽略掉三个点,就相当于:
let a = [1,2,3];
扩展运算符的应用(核心就是打散)
一、把数组打散成多个参数
经常看js奇技淫巧的同学,应该知道把数组打散成多个参数,有一个方法是用apply方法,比如,找出数组里的最大的数:
1
Math.max.apply(null, [14, 3, 77]); // 77
这种写法利用的apply的特性:foo.apply(this,arguments)==this.foo(arg1, arg2, arg3),由于apply方法比较难理解,一般使用者也比较抵触用这个方法,所以应用不多,现在好了,有了扩展运算符,ES算是给了一个官方的打散数组为参数的方法。
1
Math.max(...[14, 3, 77])
二、一次性合并多个数组
ES5确实已经有很好的合并方法.concat,所以扩展运算符只是提供了另一条路,ES6这个扩展运算符写法的好处是不借助函数,只是JS运算。
1
2
3
4
// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more] // ...会把more打散
1
2
3
4
5
6
7
8
9
10
11
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' ]
三、复制数组
由于数组赋值是传址而不是传值,所以ES5复制数组的方法是这样:
1
2
const a1 = [1, 2];
const a2 = a1.concat(); // concat会生成新的数组
现在ES6可以这么写:
1
2
3
4
5
const a1 = [1, 2];
// 写法一,利用扩展运算符
const a2 = [...a1];
// 写法二,利用剩余操作符
const [...a2] = a1;
四、将字符串打散为数组
1
2
[...'hello']
// [ "h", "e", "l", "l", "o" ]
五、打散实现了 Iterator 接口的对象
实现了 Iterator 接口的对象比如document.querySelectorAll(‘div’):
1
2
let nodeList = document.querySelectorAll('div'); // nodeList对象不是数组,而是一个类似数组对象
let array = [...nodeList]; // 真正的数组
六、打散Map和Set结构
1
2
3
4
5
6
7
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
七、打散迭代器对象
1
2
3
4
5
6
7
const go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3]
剩余操作符的应用(核心就是打包)
与扩展操作符相反,剩余操作符将多个值收集为一个变量,而扩展操作符是将一个数组扩展成多个值。
1
2
3
4
const [a, ...b] = [1, 2, 3]
console.log(a) // 1
console.log(b) // [2, 3]
1. Rest 参数接受函数的多余参数,组成一个数组,放在形参的最后,形式如下:
1
2
3
function func(a, b, ...theArgs){
// ...
}
2. Rest参数和arguments对象的区别: rest参数只包括那些没有给出名称的参数,arguments包含所有参数 arguments 对象不是真正的数组,而rest 参数是数组实例,可以直接应用sort, map, forEach, pop等方法 arguments 对象拥有一些自己额外的功能
3. 从 arguments 转向数组 Rest 参数简化了使用 arguments 获取多余参数的方法
1
2
3
4
5
6
7
// arguments 方法
function func(a, b){
var args = Array.prototype.slice.call(arguments);
console.log(args)
}
func(1,2)
1
2
3
4
// Rest 方法
function func(a, b, ...args){
// ...
}
注意,rest 参数之后不能再有其他参数(即,只能是最后一个参数),否则会报错
1
2
3
4
function func(a, ...b, c) {
// ...
}
// Rest parameter must be last formal parameter
函数的 length 属性,不包括rest参数
1
2
3
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, b, ...c)).length // 2
4. Rest参数可以被结构(通俗一点,将rest参数的数据解析后一一对应)不要忘记参数用[]括起来,因为它是数组
1
2
3
4
5
6
7
function f(...[a, b, c]) {
return a + b + c;
}
f(1) //NaN 因为只传递一个值,其实需要三个值
f(1, 2, 3) // 6
f(1, 2, 3, 4) // 6 (第四值没有与之对应的变量名)