这是一个笔记博客

分类标签

当前:首页 > JS的rest和spread操作符

JS的rest和spread操作符

2018-01-11 11:12    浏览量:0    作者

转载 http://www.jdon.com/idea/js/ecmascript-rest-spread.html

  ECMAScript 6引入三个点“...”语法用来分别代表一个数组参数列表。

rest操作符主要用于获得传递给函数的参数列表,案例代码:

function countArguments(...args) { 
  return args.length;
}
// 获得参数的数量
countArguments('welcome', 'to', 'Earth'); // => 3  

下面是spread操作符主要用于数组构造和解构,在调用时将数组填入函数参数:

let cold = ['autumn', 'winter']; 
let warm = ['spring', 'summer']; 
// 构造一个数组
[...cold, ...warm] // 真正数组值是 ['autumn', 'winter', 'spring', 'summer']


// 解构一个数组
let otherSeasons, autumn; 
[autumn, ...otherSeasons] = cold;
otherSeasons      // 值是 ['winter']  


// 代表一个数组的函数参数
cold.push(...warm); 
cold              // 值是 ['autumn', 'winter', 'spring', 'summer']  

下面我们看看为什么要使用这种数组参数?

Rest参数

  如果不使用这种三个点的数组参数新语法,过去我们是使用一个对象作为函数参数,但是会遭遇函数外和函数内访问不一致情况,如下面filterNumbers()是一个内部函数,它要访问外部的函数sumOnlyNumbers()的arguments 对象:

function sumOnlyNumbers() { 
  var args = arguments;
  var numbers = filterNumbers();
  return numbers.reduce((sum, element) => sum + element);


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

首先我们要将arguments分配给给一个临时新变量args,这样才能在内部函数filterNumbers中可以访问args新变量,因为 filterNumbers()定义了它自己的arguments 会覆盖外部的arguments 。这种做法太冗余了。

使用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接受以一个数组作为参数,因为名称冲突解决了,因此args可以使用在 filterNumbers()内部。而且引入args后,可以直接使用 args.filter()方法,这也是rest参数独特之处。

如果不是所有值都包括在rest参数中,你能在开始定义一个逗号,明确定义这些参数是不包括在rest参数中:

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]  

而arguments 对象则没有这种可选择的属性,而是包涵了所有值。

下面再看看在箭头函数的情况,一个箭头函数并不能在其内容体内定义arguments 对象,而是只能访问其闭包作用域下的那个arguments,如果你想获得所有的参数只能使用rest参数:

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

items rest参数包含了函数所有参数在一个数组中,而arguments对象是来自闭包enclosing作用域获得的,因此肯定等于outerArguments ,也就没有多大意义。

 

spread参数

  spread操作符能够用一个数组配置构造器参数:

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();

King的构造器参数是name和country,而我们可以使用三个点"..."将一个数组赋值到这个构造器,数组的数值分别对应于name和country。

此外spread操作符能够使用遍历协议接口对一个其中元素进行遍历然后收集结果。

function iterator() { 
  var index = 0;
  return {
    next: () => ({ // 遵循遍历协议Iterator protocol
    done : index >= this.length,
    value: this[index++]
  })
};
}
var arrayLike = { 
  0: 'Cat',
  1: 'Bird',
  length: 2
};
arrayLike[Symbol.iterator] = iterator; //遵循可被遍历协议
var array = [...arrayLike]; 
console.log(array); // => ['Cat', 'Bird']  

arrayLike[Symbol.iterator]会在包含遍历函数iterator()对象中创建一个属性,使得这个对象遵循可被遍历协议(itarable protocol. iterator()),返回的是一个带有next属性作为函数的对象(遵循 iteration protocol)。

因此arrayLike 现在变得可遍历了,spread操作符用于释放arrayLike中元素到一个数组中,也就是释放到数组[...arrayLike],这就是三个点语法spread参数可作为一个枚举元素的结果列表。

 

 

理解spread运算符与rest参数

spread运算符与rest参数 是ES6的新语法。
它们的作用是什么?能做什么事情?

1. rest运算符用于获取函数调用时传入的参数。

 
function testFunc(...args) {
   console.log(args);  // ['aa', 'bb', 'cc']
   console.log(args.length); // 3
}
 // 调用函数
 testFunc('aa', 'bb', 'cc'); 
 

2. spread运算符用于数组的构造,析构,以及在函数调用时使用数组填充参数列表。

 
let arrs1 = ['aa', 'bb'];
let arrs2 = ['cc', 'dd'];

// 合并数组
let arrs = [...arrs1, ...arrs2];
console.log(arrs); // ['aa', 'bb', 'cc', 'dd']

// 析构数组
let param1, param2;
[param1, ...param2] = arrs1;

console.log(param1); // aa
console.log(param2); // ['bb']
 

3. 类数组的对象转变成数组。

比如我们常见的是arguments对象,它是类数组,它有长度属性,但是没有数组的方法,比如如下代码:

 
function testFunc() {
   console.log(arguments); // ['a', 'b']
   console.log(typeof arguments); // object
   console.log(arguments.length); // 2
   console.log(arguments.push('aa')); // 报错  arguments.push is not a function
    };
 // 函数调用
 testFunc('a', 'b');
 

把类数组对象转换成数组,代码如下:

 
function testFunc() {
   // 转换成数组
   var toArray = [...arguments];
   console.log(toArray); // ['a', 'b']
   toArray.push('11');   // ['a', 'b', '11']
   console.log(toArray);
 };
 // 函数调用
 testFunc('a', 'b');
 

4. 数组的深度拷贝
浅拷贝如下demo:

var arr1 = [1, 2];
var arr2 = arr1;
arr1.push(3);
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3]

如上代码,arr1是一个数组有2个值 [1, 2], 然后把 arr1 赋值个 arr2, 接着往arr1中添加一个元素3,然后就会影响arr2中的数组。
因为我们知道浅拷贝是:拷贝的是该对象的引用,所以引用值改变,其他值也会跟着改变。
所以引用值也跟着改变。

深度拷贝对象如下代码:

var arr1 = [1, 2];
var arr2 = [...arr1];
arr1.push(3);
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2]

5. 字符串转数组
如下代码:

var str = 'kongzhi';
var arr = [...str];
console.log(arr); // ["k", "o", "n", "g", "z", "h", "i"]

如果一个函数最后一个形参以 ...为前缀的,则在函数调用时候,该形参会成为一个数组,数组中的元素都是传递给这个函数多出来的实参的值。
比如如下代码:

function test(a, ...b) {
   console.log(a); // 11
   console.log(b); // ['22', '33']
}
test('11', '22', '33');

6. 解构赋值
解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。

 
// 解构数组
var arr = ['aa', 'bb', 'cc'];
let [a1, a2, a3] = arr;
console.log(a1); // aa
console.log(a2); // bb
console.log(a3); // cc

// 对象解构
var o = {a: 1, b: 2};
var {a, b} = o;
console.log(a);  // 1
console.log(b);  // 2
 

7. 交换变量

var a = 1, b = 2;
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1

8. 从函数中返回多个值

 
function test() {
   return {
      aa: 1,
      bb: 2
   }
}
let { aa, bb } = test();
console.log(aa); // 1
console.log(bb); // 2
 

 

推荐阅读