ES6 rest parameters和spread operator

这篇文章主要记录和学习Rest parameters和Spread Operator的区别和联系。今天我去了解了下关于Rest解构赋值/扩展运算符的用法和区别。主要是开始的时候容易弄混淆。所以想要记录和学习下来。也分享给大家。


Peview

if you don’t use rest parameter

Aggregation of remaining arguments into single parameter of variadic functions.

1
2
3
4
5
6
function f(x, y) {
var a = Array.prototype.slice.call(arguments, 2);
return (x+y) * a.length;
}

f(1, 3, "hello", true, 7);

如果使用Rest Parameter就会简单很多:

1
2
3
4
5
function f(x, y, ...a){
return (x+y) * a.length;
}

f(1, 3, "hello", true, 7);

If you don’t use spread operator

Spreading of elements of an iterable collection (like an array or even a string) into both literal elements and individual function parameters.

1
2
3
4
5
6
7
var params = ["hello", true, 7];
var extends = [1, 3].concat(params);

f.apply(undefined, [1, 3].concat(params)); //因为extends是数据的原因,所以用apply

var str = "Seven";
var chars = str.split(""); //["S", "e", "v", "e", "n"]

如果使用spread operator的话:

1
2
3
4
5
6
var params = ["hello", true, 7];
var extends = [1, 3, ...params];
f(1,3, ...params);

var str = "Seven";
var chars = [...str]; //["S", "e", "v", "e", "n"]

下面具体看下两种新的 Extended Parameter Handling。


Rest parameters

The rest parameter syntax allows us to represent an indefinite number of arguments as an array.

在ES6的新特性中,Rest parameters可以为我们带来很多方便的地方。它可以帮助我们去把一系列不确定个数的参数转换成数组。


Rest parameters的语法

1
2
3
function(a, b, ...theArgs) {
// ...
}

比如我们传入 fun(1,2,3,4),那么a将为1,b将为2,theArgs将为[3,4]。


arguments和rest parameters的区别

区别主要有下面三点:

rest parameters are only the ones that haven’t been given a separate name, while the arguments object contains all arguments passed to the function;

the arguments object is not a real array, while rest parameters are Array instances, meaning methods like sort, map, forEach or pop can be applied on it directly;

the arguments object has additional functionality specific to itself (like the callee property).
-FROM MDN => rest_parameters

我觉得讲的很清楚了。具体怎么使用,我们看下面的几个例子来更好的理解这些区别。


煮几个栗子

1
2
3
4
5
6
7
8
9
10
function sum() {
var numbers = Array.prototype.slice.call(arguments),
result = 0;
numbers.forEach(function (number) {
result += number;
});
return result;
}
console.log(sum(1)); // 1
console.log(sum(1, 2, 3, 4, 5)); // 15

上面是我们最长看到的一个相加参数的方法。由于arguments不是数组,所以我们在使用前要先将他转换成数组。我们再看看rest的写法。

1
2
3
4
5
6
7
8
9
function sum(…numbers) {
var result = 0;
numbers.forEach(function (number) {
result += number;
});
return result;
}
console.log(sum(1)); // 1
console.log(sum(1, 2, 3, 4, 5)); // 15

由于使用…numbers的参数就已经是被转换成数组了,所以我们可以直接使用。

再看个几个可以直接使用rest的例子。

1
2
3
4
5
6
7
8
function multiply(multiplier, ...theArgs) {
return theArgs.map(function (element) {
return multiplier * element;
});
}

var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]

上面看两个地方,第一是传参数是从第二个开始传的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function sortRestArgs(...theArgs) {
var sortedArgs = theArgs.sort();
return sortedArgs;
}

console.log(sortRestArgs(5,3,7,1)); // shows 1,3,5,7

function sortArguments() {
var sortedArgs = arguments.sort();
return sortedArgs; // this will never happen
}

// throws a TypeError: arguments.sort is not a function
console.log(sortArguments(5,3,7,1));

再举一个经常见到的例子:

1
2
3
4
5
function logArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}

下面是更好的一种写法:

1
2
3
4
5
function logArguments(...args) {
for (let arg of args) {
console.log(arg);
}
}

好,到目前为止,你肯定很清楚,不过估计过一会可能会混淆了,就像我一样。下面我们看看spread。


Spread_operator

spread的基本语法

用于函数调用:

1
myFunction(...iterableObj);

用于数组字面量:

1
[...iterableObj, 4, 5, 6]

也就是说spread有两种情况。一个是在函数中使用的情况。一个是在数组字面量的情况。在函数中就相当于是分割成单个参数,在数组中是组成一个更大的数组。


spread和rest parameter的区别

spread和rest parameters非常像。因为它们都有…这个表达式。但是它们几乎是相反的操作。spread 可以把一个数组分割成单个的参数。然后传递给函数。而刚刚我们讲的rest则是把单个的参数转换成数组传递下去。

还有一种操作符叫做剩余操作符(the rest operator),它的样子看起来和展开操作符一样,但是它是用于解构数组和对象。在某种程度上,剩余元素和展开元素相反,展开元素会“展开”数组变成多个元素,剩余元素会收集多个元素和“压缩”成一个单一的元素。

1
2
3
4
5
function sum(a, b, c) {
return a + b + c;
}
var args = [1, 2, 3];
console.log(sum(…args)); // 6

但是在ES5中我们一般是这么做的:

1
2
3
4
5
function sum(a, b, c) {
return a + b + c;
}
var args = [1, 2, 3];
console.log(sum.apply(undefined, args)); // 6

因为apply可以帮助我们把数组变成单个的参数。这样是不是很方便了。

1
2
3
4
5
function sum(a, b, c) {
return a + b + c;
}
var args = [1, 2];
console.log(sum(…args, 3)); // 6

也可以像上面一样混淆着写。所以我们总结下。


更好的apply

我们都是使用Function.prototype.apply方法来将一个数组展开成多个参数:

1
2
3
function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);

用了展开符我们可以这样写:

1
2
3
function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);

也可以展开多个:

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


更强大的数组字面量

如果在已有的数组中的某个部分添加一个属性,通常会用到push,splice,concat 等数组方法。有了扩展运算符会让代码更简洁:

1
2
var parts = ['shoulder', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes']; // ["head", "shoulders", "knees", "and", "toes"]


更好的 push 方法

1
2
3
4
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// 将arr2中的所有元素添加到arr1中
Array.prototype.push.apply(arr1, arr2);

上面是我们一般将数组push添加到一个数组的情况。下面是我们使用spread操作符。

1
2
3
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);


将类数组对象转换成数组

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

举几个很常用的例子

如果我们有一个数组,要求它的最大值,通常我们会这么做:

1
2
3
var myArr = [5,3,9];
Math.max(myArr); // NaN
Math.max.apply(Math,myArr); // 9

一般我们都是利用apply可以把数组转换为单个参数。现在我们可以这样做:

1
2
var myArr = [5,3,9];
Math.max(...myArr); //9

是不是很方便?还有一个优点:扩展运算符支持构造函数:

1
2
3
new Date(...[2016, 5, 6]);
new Date.apply(null, [2016, 8, 2]); // TypeError: Date.apply is not a constructor
new (Function.prototype.bind.apply(Date, [null].concat([2016, 8, 2]))); //Date 2016-09-01T16:00:00.000Z


总结

主要是了解了ES6的这两个新特性。我觉得很好用。不过都是要刻意的去用才有熟悉的过程。否则估计过不了多久就忘记啦。大部分整理和学习自MDN。感谢。MDN - Spread_operator