【ES6脚丫系列】

图片.png

『ES6脚丫系列』扩展运算符spread和rest参数

学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。

扩展运算符(...)

概念

【01】又叫做展开运算符。

【02】spread 运算符和 rest 参数相反。

语法

三个点: ...

将一个数组或一个可迭代的对象,在一次调用中,将它们的内容分隔为单个单个的成员参与运算。

如果是数组:

等同于将一个数组去掉外层的方括号,然后整体放在原先的位置一样。
逗号分隔的参数列表。

如果是字符串:

等同于,变为单个字符单个字符用逗号分隔的参数列表。

let res = [..."hel"];//["h","e","l"];

如果是可迭代对象:(只有部署了iterator的对象才是可迭代的)

把属性变为用逗号分隔的参数列表。

例子:

let array = ['one', 'two', 'three']

// These two are exactly the same
console.log(...array) // one two three
console.log('one', 'two', 'three') // one two three

img


例子:

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

只想要一个数组呢?

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

用途:

【01】复制数组

slice()是JS数组的一个方法,它可以复制数组,类似的,可以使用扩展运算符来复制数组:

arr2并不等于arr。因为不是相等操作,它们引用的地址不一样。

var arr = ['a', 'b', 'c'];
var arr2 = [...arr];
console.log(arr2);// ['a', 'b', 'c']

【02】连接数组

可以使用扩展运算符替代concat()来连接数组。

首先,我们来看看concat()方法是如何实现的。

var arr = ['a', 'b', 'c'];
var arr2 = ['d', 'e', 'f'];

arr1 = arr.concat(arr2);
console.log(arr);// ['a', 'b', 'c', 'd', 'e', 'f']

使用扩展运算符:

var arr = ['a', 'b', 'c'];
var arr2 = ['d', 'e', 'f'];
arr = [...arr, ...arr2];
console.log(arr);// ['a', 'b', 'c', 'd', 'e', 'f']

【03】Math

可以在使用math函数时结合扩展运算符。

例子。

Math.max()会返回一堆数字中最大的数。

Math.max();// -Infinity
Math.max(1, 2, 3);// 3
Math.max(100, 3, 4);// 100

如果不使用扩展运算符,最简单的方式是使用.apply(),将一个数组作为参数传入Math.max()

var arr = [2, 4, 8, 6, 0];
function max(arr) {
  return Math.max.apply(null, arr);
}
console.log(max(arr));// 8

这样做很麻烦。

现在来看一下如何使用扩展运算符来得到相同的结果的。只需要两行代码:

var arr = [2, 4, 8, 6, 0];
var max = Math.max(...arr);
console.log(max);// 8
let numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1

【04】字符串转换数组

使用扩展运算符将字符串转换成数组。

var str = "hello";
var chars = [...str];
console.log(chars); // ['h', 'e',' l',' l', 'o']

【05】能够把可迭代对象(NodeList, arguments等等)转化为真正的数组。

// Convert NodeList to Array
let divsArray = [...document.querySelectorAll('div')];

// Convert Arguments to Array
let argsArray = [...arguments];

下面的例子使得一个类数组对象符合迭代协议,并利用spread运算符将其转变为一个数组:

function iterator() {
    var index = 0;
    return {
        next: () => ({ // Conform to Iterator protocol
            done : index >= this.length,
            value: this[index++]
        })
    };
}
var arrayLike = {
    0: 'Cat',
    1: 'Bird',
    length: 2
};
arrayLike[Symbol.iterator] = iterator; //Conform to Iterable Protocol
var array = [...arrayLike];
console.log(array); // => ['Cat', 'Bird']

【02】散布操作符 (…)

概念

【01】rest参数。用三个点表示。

【02】rest 参数意味着把剩下的东西包装成一个数组。

【03】用在函数参数和解构数组中。

它将一个逗号分隔的参数列表转换成一个数组。

吃码小妖:类似打包和解压的既视感?

如果在函数形参中使用了rest参数(打包),那么在函数中使用spread使用它(解压),等于原封不动的使用实参了。

【04】当三个点出现在函数参数时,它意味着将调用函数时的参数列表变为一个数组。该参数名就是这个数组。

如果函数参数本身就是一个数组,那么rest参数运算符等于把函数形参变为一个二维数组了。

例子:

function a(...args){}
a(1,2,3,4,5);

等同于

function a(){
  var args = [arguments[0],arguments[1],...,arguments[N]];
};

a(1,2,3,4,5);

【05】如果是多个形参,那么rest参数需要是参数列表中的最后一个参数。写成逗号分隔的参数列表。

arguments对象不具有这种选择性并且始终包含所有的参数值。

例子:

function filter(type, ...items) {
    return items.filter(item => typeof item === type);
}
filter('boolean', true, 0, false);        // => [true, false]
filter('number', false, 4, 'Welcome', 7); // => [4, 7]

用途

【01】使用不定数量的函数参数。

在ES5中,当我们需要处理一个未知数量的参数的函数时,可以使用arguments变量。

function sum () {
  console.log(arguments)
}

sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);//55

图片.png

计算这个参数总和的一种方法是将其转换成具有 Array.prototype.slice.call(arguments) 的数组,然后用数组方法循环遍历每个数字,如 forEach 或 reduce。

我相信你可以自己实现 forEach ,所以这里是 reduce 的例子:

// ES5 way
function sum () {
  let argsArray = Array.prototype.slice.call(arguments);
  return argsArray.reduce(function(sum, current) {
    return sum + current;
  }, 0)
}

使用 ES6 rest 参数,可以将所有逗号分隔的参数直接打包到数组中。

// ES6 way
const sum = (...args) => args.reduce((sum, current) => sum + current, 0)

// ES6 way if we didn't shortcut it with so many arrow functions
function sum (...args) {
  return args.reduce((sum, current) => sum + current, 0)
}

【】在数组解构中遇到三个点。它会将剩余的元素打包变为一个数组。

let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third] = scores

console.log(first) // 98
console.log(second) // 95
console.log(third) // 93

如果我们想要 rest 的分数,我们可以通过将剩余的分数打包成一个数组。

let scores = ['98', '95', '93', '90', '87', '85']
let [first, second, third, ...restOfScores] = scores

console.log(restOfScores) // [90, 97, 95]

用途2:在数组中插入不定数量的元素。

例如,.push(item1, ..., itemN)会把元素一个接一个的插入数组:不得不循环每个元素将其作为参数。但这并不总是很方便:有时需要把一整个数组的元素push到目标数组。

在ES5中这可以通过.apply()做到:用一种不友好且繁琐的方式。让我们看看:

var fruits = ['banana'];
var moreFruits = ['apple', 'orange'];
Array.prototype.push.apply(fruits, moreFruits);
console.log(fruits); // => ['banana', 'apple', 'orange']
ES6:let res = fruits.push(...moreFruits);console.log(res);// ['banana', 'apple', 'orange']

【】如何更好的把数组中的元素作为参数填充到函数调用中。

ES5在函数对象上提供了 .apply()来解决这个问题。不幸的是这项技术有3个问题:

  • 它需要手工的指定函数调用的上下文。
  • 它不能使用在构造器函数调用中。
  • 人们倾向于一个更短的解决方案。

例子:

let countries = ['Moldova', 'Ukraine'];
countries.push.apply(countries, ['USA', 'Japan']);
console.log(countries); // => ['Moldova', 'Ukraine', 'USA', 'Japan']

例子:

function spreadReporter(...values) {
  let object = [...values];
  return object[0].length;
}

var items = ['one', 'two', 'three'];

console.log(spreadReporter(items)); //  3

例子:

let dairy = [];
let store = {
  add: function(category, ...items) {
    category.push(...items);
  }
};

store.add(dairy, 'milk', 'sour cream');
store.add(dairy, 'ice cream', 'yogurt', 'cheese');
console.log(dairy);

// outputs ["milk", "sour cream", "ice cream", "yogurt", "cheese"]

在复杂情景中的函数体内操作arguments对象是很麻烦的。

为了在filterNumbers()访问sumOnlyNumbers()的arguments,你不得不创建一个临时变量args。这是因为filterNumbers()会定义它自己的arguments从而覆盖了外层的arguments。

这种方式可以工作,但是太繁琐了。

function sumOnlyNumbers() { function filterNumbers() {
        return Array.prototype.filter.call(args, element => typeof element === 'number');
    }
    var args = arguments;
    var numbers = filterNumbers();
    return numbers.reduce((sum, element) => sum + element);
   
}
sumOnlyNumbers(1, 'Hello', 5, false); // => 6

rest运算符可以优雅的解决这个问题。它允许你在函数声明时定义一个rest参数...args:

function sumOnlyNumbers(...args) {
    var numbers = filterNumbers();
    return numbers.reduce((sum, element) => sum + element);
    function filterNumbers() {
        return args.filter(element => typeof element === 'number');
    }
}
sumOnlyNumbers(1, 'Hello', 5, false); // => 6

函数声明function sumOnlyNumbers(...args)表明args以数组的形式接受调用参数。


【】箭头函数并不定义自己的arguments而是会访问外层作用域中的arguments对象。

例子:

(function() {
    let outerArguments = arguments;
    const concat = (...items) => {
        console.log(arguments === outerArguments); // => true
        return items.reduce((result, item) => result + item, '');
    };
    concat(1, 5, 'nine'); // => '15nine'
})();

Spread运算符可以在构造器调用中使用数组元素作为参数,而这并不能通过直接使用.apply()做到。

让我们看一个例子:

class King {
    constructor(name, country) {
        this.name = name;
        this.country = country;
    }
    getDescription() {
        return `${this.name} leads ${this.country}`;
    }
}
var details = ['Alexander the Great', 'Greece'];
var Alexander = new King(...details);
Alexander.getDescription(); // => 'Alexander the Great leads Greece'

吃码小妖
43 声望3 粉丝