FrankKai.github.io
FrankKai.github.io copied to clipboard
es6之rest parameters和spread syntax
在学习nodeschool的count-to-6教程时,遇到了这两个常用但是一直没有系统学习的概念,因此在mdn找到相关文档进行学习。
-
Rest parameters
- rest parameters语法
- rest parameters与arguments的区别
- rest parameters没出现前,es5怎么做?
- rest parameters既然是数组,可以直接解构吗?
- 优雅的使用rest parameters的例子
-
Spread syntax
- 几种spread场景
- function calls中的spread语法
- array literals中的spread语法
- 在可迭代对象上的spread
- Rest syntax与Spread syntax
-
...
操作符实践- 通过...操作符克隆出的数组和对象,是浅拷贝还是深拷贝?
- Math.max,Math.min取最大最小值
- 函数形参为对象类型结构
- 函数形参为数组类型结构
- 总结与思考
rest parameters
rest parameters语法
rest parameter语法允许我们将不确定的arguments当作数组。
function sum(...theArgs) {
return theArgs.reduce((acc, cur)=> acc+cur))
}
console.log(sum(1, 2, 3)); // 6
语法:function f(a, b, ...theArgs){ //... }
函数的最后一个形参可以使用...前缀把剩余的arguments存储在一个标准的js数组中。 只有最后一个参数是“rest parameter”。
rest parameters与arguments的区别
- rest parameters仅仅包含了那些没有被命名的剩下的形参,而arguments包含了全部的形参
- arguments是一个类数组但不是数组,rest parameters是Array实例,意味着可以使用sort,map,forEach等等方法
- arguments对象有额外的属性,例如callee
rest parameters没出现前,es5怎么做?
在rest parameters出现之前,可以使用以下几种方式将arguments转化为数组。
function f(a, b) {
let normalArray = Array.prototype.slice.call(arguments)
// --or--
let normalArray = [].slice.call(arguments)
// --or--
let normalArray = Array.from(arguments)
}
rest parameters既然是数组,可以直接解构吗?
当然可以。
function f(...[a, b, c]) {
return a + b + c;
}
f(1) // NaN (b and c are undefined) f(1, 2, 3) // 6 f(1, 2, 3, 4) // 6 (the fourth parameter is not destructured)
优雅的使用rest parameters的例子
function multiply(multiplier, ...theArgs) {
return theArgs.map(function(element) {
return multiplier * element
})
}
let arr = multiply(2, 1, 2, 3)
console.log(arr) // [2, 4, 6]
// 自我思考:用好rest parameters,避免Array.prototype.forEach到底。
Spread syntax
几种spread场景
- function calls 展开arguments
myFunction(...iterableObj);
- array or strings literals 展开元素
[...iterableObj, '4', 'five', 6];
- object literals 展开key-value对 es2018
let objClone = { ...obj };
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));
// expected output: 6
console.log(sum.apply(null, numbers));
// expected output: 6
function calls中的spread语法
spread语法可以替代apply
function myFunction(x, y, z) { }
const args = [0, 1, 2];
// 可以使用apply将一个普通数组转换为函数的arguments
myFunction.apply(null, args);
有了spread语法后,可以这样写:
function myFunction(x, y, z) { }
const args = [0, 1, 2];
myFunction(...args);
spread语法可以用很多次:
function myFunction(v, w, x, y, z) { }
const args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);
new操作符中的spread语法
apply有[[Call]]没有[[Construct]]。
const dateFields = [1970, 0, 1];
const d = new Date(...dateFields);
若是没有spread 语法,需要按照下面的方式为new使用数组:很复杂。
array literals中的spread语法
基于原有数组创建新数组更方便,不用组合使用push,splice,concat
const parts = ['shoulders', 'knees'];
const lyrics = ["head", ...parts, "and", "toes"];
// ["head", "shoulders", "knees", "and", "toes"]
复制一个数组
const arr = [1, 2, 3];
const arr2 = [...arr]; // 与arr.slice()效果一样
arr2.push(4); // arr2 [1,2,3,4] arr [1,2,3]
更好的连接数组的方式
const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
// Append all items from arr2 onto arr1
arr1 = arr1.concat(arr2);
使用spread 语法后:
const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2];
将数组的每一项都放置在某个数组前面:Array.prototype.unshift()
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
// Prepend all items from arr2 onto arr1
Array.prototype.unshift.apply(arr1, arr2)
// arr1 is now [3, 4, 5, 0, 1, 2]
用spread语法后,会异常简单:
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1];
// arr1 is now [3, 4, 5, 0, 1, 2]
在可迭代对象上的spread
对象的浅拷贝和合并,有了比Object.assign()更加方便的方法。
const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13 };
const clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }
const mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }
Object.assign()会出发setters,而spread语法不会。
spread语法仅仅作用于iterables
Objects本身不可迭代,只有一下几种才迭代:
- 在Array中使用
- 使用迭代函数map(), reduce(), assign()
- 合并两个对象时,会假设另一个迭代函数被使用了
下面的方式使用会报错:
const obj = {'key1': 'value1'};
const array = [...obj]; // TypeError: obj is not iterable
Rest syntax与Spread syntax
- rest看起来相同,但是rest主要用于解构数组和对象。
- rest parameters是spread syntax的对立面。spread是扩展,rest是收集。
- spread syntax “展开”一个数组到多个元素;rest parameters收集多个元素到一个元素中。
...操作符实践
通过...操作符克隆出的数组和对象,是浅拷贝还是深拷贝?
亲测。浅拷贝。
数组验证:
var foo = [1,2,3,{hi:'es5'}];
var bar = [...foo];
bar[3].hi = 'es6';
bar; // [1,2,3,{hi:'es6'}]
foo; // [1,2,3,{hi:'es6'}] 注意这里,由于...是浅拷贝,所以foo也跟着变成es6了
对象验证:
var foo = {hi: {version:'es5'}};
var bar = [...foo];
bar.hi.version = 'es6';
bar; // {hi: {version:'es6'}};
foo; // {hi: {version:'es6'}}; 注意这里,由于...是浅拷贝,所以foo也跟着变成es6了
Math.max,Math.min取最大最小值
const arr = [2,1,3];
// 取最大值
Math.min(...arr)
// 取最小值
Math.max(...arr)
函数形参为 对象类型 解构
let obj = {foo: 1, bar: 2, baz: 3, oof: 4}
function destructObj({foo, bar, ...others}){
console.log(foo, bar, others);
}
destructObj(obj);
// 1 2 {baz: 3, oof: 4}
函数形参为 数组类型 解构
let arr = [1,2,3,4,"foo"]
function destructArr([num1, num2, ...others]){
console.log(num1, num2, others);
}
destructArr(arr);
// 1 2 [3, 4, "foo"]
总结与思考
- rest parameter是收集剩余,快捷转换arguments为数组
- spread syntax是展开组合,替代apply,向new构造器传值,快捷复制和重组数组和对象
- 二者都是以...的形式存在,无须过于关注其区别,关注如何简化代码,关注如何使得代码更优雅