在现代JavaScript开发中,ES6引入的拓展运算符(Spread Operator)和剩余运算符(Rest Operator)让代码更加简洁和灵活。
无论是数组、对象的拆分与合并,还是函数参数的处理,这两个运算符都是非常实用的工具。
拓展运算符(Spread Operator)由三个连续的点 ...
表示,用于将一个可迭代对象(例如数组、字符串等)展开成多个元素。
拓展运算符可以应用在数组或对象上,实现数据的拆分、复制、合并等操作。
拓展运算符号主要应用于以下三个场景:
使用演示
let arr = ["hello","happy"]
let arr1 = ["ok","no",...arr]
代码讲解
...可迭代对象
,例如数组,相当于遍历该迭代对象,得到每一个元素。
let arr1 = ["ok","no",...arr]
等价于
let arr1 = ["ok","no"]
for(let i=0;i<arr.length;i++){
arr1.push(arr[i])
}
更形象的理解是,直接把数组元素铺开放入,所以叫做拓展运算符。
使用演示
let obj = {
name:"zhansan",
age:19
}
let student = {
...obj,
gender:"man",
}
代码讲解
...对象
,相当于遍历对象,将该对象展开,得到每一组属性和值。
let student = {
...obj,
gender:"man",
}
等价于
let student = {gender:"man"}
for(let key in obj){
student[key] = obj[key]
}
本质上就是拓展原对象,然后给新对象增加属性。所以遇到已存在的属性,新出现的会覆盖之前的,例如
let tmp1 = {
age:19
}
let tmp2 = {
age:25
}
let res = {
...tmp1,
...tmp2
}
最终结果为
//因为按照展开顺序,
// ...tmp1时,res[age] = 19
// ...tmp2时,重新赋值,res[age]=25
res = {
age:25
}
凡是可以遍历的对象都可以使用拓展运算符号。
js中都是对象,字符串也属于可遍历的对象。
let str = "hello"
let arr = [...str]
console.log(arr);//["h","e","l","l","o"]
本质上还是将数组和对象展开,
let arr = [1,2,3]
//只接收一个参数
function single(arr){
return arr
}
//接收多个参数
function more(a,b,c){
return a+b+c
}
传参示例
//直接传递整个数组作为参数
single(arr)
//将数组元素展开后再传入
more(...arr)
//相当于
more(1,2,3)
传入引用类型
其实传入整个数组和拓展数组再传入是有不同的应用场景的。
let arr = [1,2,3]
function getSum(nums){
nums[1] = 5
return nums.reduce((a,b)=>a+b,0)
}
//由于数组是引用类型,所以我们传入后,函数内对数组的修改会影响到外部原始数组
getSum(arr)
console.log(arr);//[5,2,3]
而拓展运算符传入时,我们内部再改变参数,如果该参数不是引用类型则对外部没有影响,例如
let arr = [1,2,3]
//对于参数数量,未来可以使用剩余运算符或者内置arguments对象
function getSum(a,b,c){
a = 5
return a+b+c
}
getSum(...arr)
console.log(arr)//[1,2,3]
位置参数
当我们传递拓展元素时,会按照位置进行传递。
let arr = [1,2,3]
function getVal(a){
console.log(a)
}
getVal(...arr)
//最终调用相当于 getVal(1)
//因为函数只接收一个参数,多于的参数会被忽略
拓展运算符就等价于遍历可迭代的对象,将原对象的每一项提取,所以他是一个浅层拷贝。
let arr = [1,2,{age:19}]
在数组arr
中有一个引用类型的元素{age:19}
,当我们进行...arr
时,例如
let arr1 = [...arr]
//本质上对于{age:19}执行的是如下操作
let tmp = {age:19}
arrNew.push(tmp)
而引用类型赋值时传递的是引用,所以arr
与arrNew
中的{age:19}
,指向同一个引用。
验证
let arr = [1,2,{age:19}]
let arrNew = [...arr]
//变动前
console.log(arr)//[1,2,{age:19}]
console.log(arrNew)//[1,2,{age:19}]
//获取对象并重新赋值
arrNew[2]['age'] = 25
//验证
console.log(arr)//[1,2,{age:25}]
console.log(arrNew)//[1,2,{age:25}]
在mdn的百科中被称为剩余参数
、剩余属性
。
在函数传参时,常常会出现参数不确定的情况,传统的做法是使用函数内置的arguments
对象,例如:
function getSum(){
//arguments是函数内置对象,是一个类数组,用于接收所有参数
console.log(arguments)
}
arguments
是函数内置对象,是一个类数组,用于接收所有参数。
我们可以通过遍历该对象来对所有参数进行处理,例如
function getSum(){
let sum = 0
for(let i =0;i<arguments.length;i++){
sum += arguments[i]
}
return sum
}
getSum(1) //1
getSum(1,2,3,4)//10
特点
函数的参数(无论有无形参)最终都会被arguments
对象收集。
局限性
1.arguments
对象是古老产物,是个类数组,很多新的针对迭代对象的方法是用不了的(因为迭代对象是后来加入的),例如map
、forEach
等方法。
2.箭头函数没有内置arguments
对象(因为现代js更推荐使用剩余运算符)。
剩余运算符(Rest Operator)也是由 ...
表示,但在函数参数或解构赋值中使用,通常用来接收不确定数量的参数或对象属性,并将其“收集”到一个数组或对象中。
其实
arguments
对象,可以借助拓展运算符,例如[...arguments]
变成真数组.这可能就是剩余运算符的最初含义。
剩余运算符的主要运用场景:
当我们不确定函数参数数量时,可以用...变量
来收集多有的参数,该变量会成为一个真数组,收集所有未接收参数。
function getSum(a,...nums){
console.log("第一个参数为",a)
console.log("其他参数为",nums)
}
//函数调用
getSum(10,11,1,2,5)
运行结果
第一个参数为10
其他参数为[11,1,2,5]
解构对象时,如果我们想要用一个对象接收所有剩余的属性,可以使用{...变量}
,例如
let objOrigin = {
value:"hello",
label:"你好",
order:5
}
//解构
const {val,...others} = objOrigin
console.log(val);//"hello"
console.log(others);//{ label:"你好",order:5 }
解构数组时,可以用一个数组收集所有剩余元素。
let arrOrigin = ["javascript","java","C++","Rust","Golang"]
const [first,...coding] = arrOrigin
console.log(first);//"javascript"
console.log(coding);//["java","C++","Rust","Golang"]
由于剩余运算符是接收所有剩下的元素,所以放在接收的最后,其后不应该再出现其他接收者,例如:
错误示范
//此处的形参happy是不可能接收到内容的
function getVal(...args,happy){
...
}
...
可遍历的对象不一定可以迭代,例如伪数组、字符串。
但是可迭代对象一定可以遍历,例如数组。
二者可以看作是逆运算,拓展运算符是将可遍历的对象展开成一个个元素。
剩余运算符号是将一个个元素接收,拼接成可遍历的对象。