写在前面
学习一个知识点前,首先要弄清楚学习这个知识点是什么(what),为什么要用这项技术(why),优势是什么,这项技术怎么使用(how),依照这种思路去学习,效率会相对高一些,而且会加深理解,下面结合mdn官网对扩展运算符的知识进行总结和学习
(一) .什么是 扩展运算符(What)
直接上mdn , mdn上给出的概念是这样的 ,详情参考链接mdn
结合上面的图片 ,可以总结适用的情况有以下三种
1.可以在函数调用时,将数组表达式或者String在语法层面展开(其实不止数组,String,只要...后面是可迭代对象都就可以)
即形如myfunction(...iterableObj) , 其中iterableObj译为可迭代对象(原型上具备Symbol.iterator方法的对象) , 并且...后面的对象只能为可迭代对象
(
ps:原生的iterableObj ( 即原型上具备Symbol.iterator方法(调用该方法会返回一个遍历器对象) ) 对象有:
Array , Map , Set, String , TypedArray , 函数的arguments对象 , NodeList对象,
(本质原理上: 扩展运算符会默认调用变量原型链上面的Symbol.iterator方法,返回一个遍历器对象,扩展运算符其实对这个遍历器对象执行扩展运算符)
所以换句话说_,函数(数组中使用同理)里面...后面的对象进一步引深为可以直接是一个遍历器对象(见下方Generator例子)_
)
这里注意,
这里...后面的对象只能为可迭代对象,否则会报错
//例子一:obj原型链上不具备Symbol.iterator接口,所以会报错
var obj = {'key1','value1'};
function myfunction(){
console.log(...arguments)
}
myfunction(...obj);//TypeError: Found non-callable @@iterator
//例子二:Array本身具备iterator接口,调用会返回一个遍历器对象, 故可以成功调用
var arr = ['key1','value1'];
function myfunction(){
console.log(...arguments)//key1 value1
}
myfunction(...arr);
2.和1中的类似,可以在数组构造时使用时,将数组表达式或者string在语法层面展开_(其实不止数组,String,只要...后面是可迭代对象就可以)_
即形如 [...iterableObj, '4', ...'hello', 6] , 其中iterableObj翻译为可迭代对象 , 并且...后面的对象只能为可迭代对象
ps( 原理如同上面的1中函数中使用的原理)
这里同样要注意,
这里...后面的对象只能为可迭代对象,否则会报错
//例子一:obj原型链上不具备Symbol.iterator接口,所以会报错
var obj = {'key1': 'value1'};
var array = [...obj]; // TypeError: obj is not iterable
//例子二:Array本身具备Symbol.iterator接口,调用会返回一个遍历器对象, 故可以成功调用
var arr = ['key1','value1'];
var array = [...arr]; // ['key1','value1'];
3.可已在构建字面量对象时,将对象表达式按照键值对的方式展开
其中 构造字面量对象时,进行克隆或者属性拷贝(这是ECMAScript 2018规范新增特性):
即形如 let objClone = { ...obj };
注意:这个地方,不要与上面1和2中的情况混淆 , 1和2中的...后面必须要是可迭代对象
而这里只是 ECMAScript 2018规范对 字面量对象增加了展开特性 这个新增特性
上面的3个点是从概念层次上面总结对扩展运算符运用的三种情况**,**
下面通过常用的例子对以上3种情况进行展开,以便理解扩展运算符在上述三种情况中如何使用,以及为何要使用扩展运算符
(二).怎么使用扩展运算符**(How)以及为什么使用扩展运算符(Why)**
1.在函数调用时使用( 对应(一)中的场景1****形如myfunction(...iterableObj) )
1.1 用于普通的函数调用
注意 :在函数调用时,扩展运算符必须要形如 myfunction(...iterableObj)****,即必须要用一个括号包起来.多包括号,或者不包括号都会报错
var arr=...[1,2,3]; //Uncaught SyntaxError: Unexpected token '...'
console.log((...[1,2,3])) //Uncaught SyntaxError: Unexpected token '...'
console.log(...[1,2,3]) //1,2,3
下面为方便演示,用console.log(...iterableObj)函数进行,这里的 console.log(...iterableObj)就形如上面的myfunction(...iterableObj)
//数组的情况
console.log(...[1,2,3]) //1,2,3
console.log(0,...[1,2,3],4) //0,1,2,3,4
//字符串
console.log(...'abc') //a,b,c
//Generator
let go=function *(){
yield 1;
yield 2
}
console.log(...go()) //1,2 //调用Generator本身是会直接返回一个遍历器对象,所以直接进行操作
//NodeList
console.log(...document.querySelectorAll('div')) //<div>,<div>,...,<div>
//arguments
(function(){
console.log(...arguments) //1,2,3
})(1,2,3)
//Map //Map本身是iterableObj可迭代对象,即具备Symbol.iterator接口,调用会返回一个遍历器对象
//不仅如此,Map的原型链上面调用keys方法和values方法也会返回一个可迭代对象,即具备Symbol.iterator接口,调用会返回一个遍历器对象
let map=new Map(
[
[1,'one'],
[2,'two'],
[3,'three'],
]
)
console.log(...map);//[1, "one"],[2, "two"],[3, "three"]
console.log(...map.keys());//1,2,3
console.log(...map.values());//"one","two","three"
1.2 替代函数的apply方法,
我们知道apply方法本身的用处是改变函数内部的this指向,顺便附带打散数组的功能,
所以有时我们在使用apply方法时,只是用到它打散数组的功能(而没有用到它改变函数内部this指向的功能,
但我们也不得不按照apply约定俗成的方式去使用它),所以这样使用起来不伦不类 ,
但是再es6引入扩展运算符之后,这类的问题得到了解决
//求数组的最大值
//es5
Math.max.apply(null,[14,5,77]) //14
//max方法内部本无this属性,并且max()参数不能接受数组,所以我们不得不间接使用apply打散数组的特性,这样书写不伦不类
//es6 扩展运算符
Math.max(...[14,5,77]) //14
//这种书写,既简便又优雅,这就是为什么要使用扩展运算符的原因
2**.在数组构造时使用时**(对应(一)中的场景2形如[...iterableObj, '4', ...'hello', 6])
2.1 用于构建数组
//数组的情况
console.log([...[1,2,3]]) //[1, 2, 3]
console.log([0,...[1,2,3],4]) //[0, 1, 2, 3, 4]
//字符串
console.log([...'abc']) //["a", "b", "c"]
//Generator
let go=function *(){
yield 1;
yield 2
}
console.log([...go()]) //[1, 2]
//NodeList
console.log([...document.querySelectorAll('div')]) //[<div>,<div>,...,<div>]
//arguments
(function(){
console.log([...arguments]) //[1, 2, 3]
})(1,2,3)
//Map //Map本身是iterableObj可迭代对象,即具备Symbol.iterator接口,调用会返回一个遍历器对象
//不仅如此,Map的原型链上面调用keys方法和values方法也会返回一个可迭代对象,即具备Symbol.iterator接口,调用会返回一个遍历器对象
let map=new Map(
[
[1,'one'],
[2,'two'],
[3,'three'],
]
)
console.log([...map]);//[ [1, "one"],[2, "two"],[3, "three"] ]
console.log([...map.keys()]);//[1, 2, 3]
console.log([...map.values()]);//["one","two","three"]
2.2 用于数组的拷贝(浅拷贝)
在扩展运算符出现之前
拷贝一个数组,我们可能采取下面的方式
//es5
const a1=[1,2];
const a2=a1.concat();
运用扩展运算符,,可以简化上面的操作
//es6
const a1=[1,2];
const a2=[...a1];
注意:扩展运算符对数组的拷贝只是简单的浅拷贝 ,即数组内部是基本数据类型 , 当数组中出现引用数据,类型时,就不好使了
var a = [{name:"张三"}];
var b = [...a];
a[0]==b[0];//true;
a[0].name="赵四";
console.log(b);//[{name:"赵四"}] //改变a的同时,b中的数据也随之改变
//同样这种问题也会出现在二维数组的拷贝中
var a = [[1],[2]];
var b = [...a];
a[0]==b[0];//true
//如何解决深拷贝问题? 利用JSON.parse(JSON.stringify)
var a = [[1],[2]];
var b = JSON.parse(JSON.stringify(a));
a[0]==b[0];//false
2.3 用于数组的合并
在扩展运算符出现之前
合并一个数组需要可能这样操作
let a=[1,2,3];
let b=[4,5,6];
let c=[];
c.concat(a,b);//[1,2,3,4,5,6]
利用扩展运算符,可以简化上面的操作
let a=[1,2,3];
let b=[4,5,6];
let c=[];
c=[...a,...b];//[1,2,3,4,5,6]
3.可已在构建字面量对象时,将对象表达式按照键值对的方式展开( 对应(一)中的场景3 即形如 let objClone = { ...obj } )
(
ps: 上面的这张图是mdn对其做出的解释,这里可能有疑惑,刚才1(在函数)和2(在数组)中不是说扩展运算符...后面必须要跟iterableObj吗,为啥这次后面又可以跟Object类型的对象了呢,
Object类型的对象的原型链上不具备Symbol.iterator接口呀???????????,
这里注意 扩展运算符在1(在函数)和2(在数组)中确实必须后面要跟iterableObj,这个是毋庸置疑的,但是不要这个和3中的情况不要混淆,
3中的情况是 ECMAscript提议(stage 4) 字面量对象增加了展开特性。其行为是, 将已有对象的所有可枚举(enumerable)属性拷贝到新构造的对象中,可以理解为是这是ECMAscript对Object类型的 对象授予的新特性, 和 Object类型的对象的原型链上不具备Symbol.iterator接口这个原理没有任何的关系
)
通过上面的定义我们可以看出,新特性的提出为了达到 拷贝可枚举的属性到一个新对象 的目的,注意这里的拷贝,也仅是浅拷贝而已,如果拷贝的对象中,出现了引入数据类型的属性时,还要考虑进行深拷贝
在扩展运算符出现之前,如果要浅拷贝一个对象,可能会这样操作
let obj={
name:"张三",
age:"24"
}
let newObj={}
for(var key in obj){
newObj[key]=obj[key];
}
//利用Object.assign方法
let obj={
name:"张三",
age:"24"
}
let newObj=Object.assign({},obj);
扩展运算符出现之后,大大简化了操作
let obj={
name:"张三",
age:"24"
}
let newObj={...obj}
//代码简洁优雅
还可以用于多个对象之间属性的合并
let obj1={
name:"张三",
age:"24"
}
let obj2={
sex:"男"
}
let obj3={
job:"搬砖",
}
//es5
let newobj=Object.assign({},obj1,obj2,obj3)//{name: "张三", age: "24",sex:"男", job: "搬砖"}
//es6
let newObj={...obj1,...obj2,...obj3} //{name: "张三", age: "24",sex:"男", job: "搬砖"}