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