剩余参数:
1、剩余参数是什么:
举个剩余参数的例子:
const add = (x, y, z, ...args) => {};
在这个例子中前面三个点后面跟一个参数名的就是剩余参数。
2、剩余参数的本质:
通过代码进行演示:
const add = (x, y, ...args) => {
console.log(x, y, args); // 在打印剩余参数的时候不要带前面的三个点
};
// 通过传不同的参数进行演示:
add(); // undefined undefined []
add(1); // 1 undefined []
add(1, 2); // 1 2 []
add(1, 2, 3, 4, 5); // 1 2 (3) [3, 4, 5]
由上例得出的结论:剩余参数永远是个数组,即使没有值,也是空数组。此外,将剩余的参数装进一个数组。
剩余参数的注意事项:
1、箭头函数的剩余函数:
箭头函数的参数部分即使只有一个剩余参数,也不能省略圆括号。
const add = (...args) => {};
2、使用剩余参数代替arguments 获取实际参数:
普通函数可以通过arguments获取全部参数:
const add = function () {
console.log(arguments);
}; // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
但箭头函数没有arguments所以无法使用arguments但可以使用剩余参数:
const add = (...args) => {
console.log(args);
};
add(1, 2); // (2) [1, 2]
3、剩余参数的位置:
剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错。
举个错误的例子供大家参考:
const add = (x, y, ...args,z) => {
console.log(args);
}; // Rest parameter must be last formal parameter
在这个例子中就会报剩余参数不是最后一个参数的错误!请大家谨记剩余参数只能是最后一个参数这条规则!
剩余参数在实际开发中的应用:
1、完成 add 函数:
该函数在不确定函数值的情况下可以计算出参数相加后的值:
const add = (...args) => {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
};
console.log(add()); // 0
console.log(add(1,2,3)); // 6
上面的那种写法其实很常见。
2、与解构赋值结合使用:
(1)与数组解构赋值的结合:
剩余参数不一定非要作为函数参数使用。
举一个剩余参数用于解构赋值的例子:
const [num, ...args] = [1, 2, 3, 4];
console.log(args); // 1 Array(3)
但注意,剩余参数与解构赋值结合使用时也必须是最后一个:
如果不放最后一个,举个错误例子:
const [...args,num] = [1, 2, 3, 4];
console.log(num, args);
// 在这里会报错误!切记这点!
下面的代码例子是将剩余参数与解构赋值结合在一起然后当作函数参数使用的案例:
const func = ([num, ...args]) => {console.log(num,args);};
func([1, 2, 3]); // 1 (2) [2, 3]
(2)与对象解构赋值的结合:
同样!用代码说话!
const { x, y, ...z } = { a: 3, x: 1, y: 2, b: 4 };
console.log(x,y,z); // 1 2 {a: 3, b: 4}
同样剩余参数必须是最后一位!这里就不再举例了,大家可以自行举例证明!
同样对象的解构赋值也可以像数组一样用作函数的参数,举例代码如下:
const func = ({ x, y, ...z }) => {};
func({ a: 3, x: 1, y: 2, b: 4 });
数组展开运算符:
1、数组展开运算符的基本用法:
在讲基本用用法之前先出一道题目:给一个里面全是数字的数组,求该数组中最小的值。
解决这个问题可以用自己写一个函数去求,但这种做法太麻烦了,在JS中有一个Math.min()
的内置函数可以求参数的最小数,但痛点是该方法不能传数组,具体见代码:
// 这种传参形式可以:
console.log(Math.min(2,3,5,1,7)); // 1
//传数组的方法不会返回最小值:
console.log(Math.min([2,3,5,1,7]); // NaN
但,一旦运用展开运算符那么这个问题就迎刃而解了,代码如下:
console.log(Math.min(...[2,3,4])); // 2
看到这样代码后可能大家对剩余参数和展开运算符开始迷惑了,它们两个是一样吗?怎么形式上一样?这里大家不用着急,下面开始讲剩余参数和展开运算符的区别。
2、分辨剩余参数和展开运算符:
(1)根本区别:
展开运算符是将数组展开成一个一个元素,举例:将 [3,1,2] 展开成 3,1,2 这种形式,而剩余参数正好相反,而是把 3,1,2 这种形式转换成 [3,1,2] 这种数组的形式。
(2)区分剩余参数和展开运算符:
话不多说,直接上代码演示
// 试问下面打印的是剩余参数还是展开运算符:
function fn(...args) {
console.log(args); // (3) [1, 2, 3]
// 毫无疑问,这里打印的是剩余参数,是数组的形式
console.log(...args); // 1 2 3
// 这个打印的就是展开运算符,因为args是个数组,在其前面加三个点就变成了展开运算符的形式
}
fn(1,2,3);
再举个例子:
console.log([...[1, 2, 3], 4]); // (4) [1, 2, 3, 4]
这个例子也很好理解,…[1,2,3] 是展开运算符, …[1,2,3] 展开后就是 1,2,3 所以打印的是 [1,2,3,4]
3、数组展开运算符的应用:
(1)用来复制数组:
不用展开运算符的复制数组:
const a = [2,3,4];
const b = a;
console.log(b); // (3) [2, 3, 4]
这种写法看似复制了数组,实则上还是有弊端的,大家都知道数组 a 和 b 都指向同一个堆内存,那么一个数组里面的值一旦发生改变,那么另一个数组里面的值也会相应发生改变,这就是一个不太好的地方,但如果用展开运算符就可以很好的避免这个问题出现:
const a = [1, 2];
const c = [...a]; // 用展开运算符为其赋值
// 上式的c = [...a] 其实就相当于 const c = [1, 2];
a[0] = 3; // 改变a数组里面的值
console.log(a); // [3,2]
console.log(c); // [1,2]
用展开运算符轻松解决问题。
(2)合并数组:
const a = [1, 2];
const b = [3];
const c = [4, 5];
// 这里就很方便了,想怎么合并怎么合并
console.log([...a, ...b, ...c]); // (5) [1, 2, 3, 4, 5]
console.log([...b, ...a, ...c]); // (5) [3, 1, 2, 4, 5]
console.log([1, ...b, 2, ...a, ...c, 3]); // (8) [1, 3, 2, 1, 2, 4, 5, 3]
(3)字符串转化成数组:
字符串可以按照数组的形式展开。
console.log(...'alex'); // a l e x
console.log([...'alex']); // (4) ["a", "l", "e", "x"]
// 利用这个方法可以轻松的将字符串转化成数组
(4)常见的类数组转化为数组:
arguments:
function func() {
console.log([...arguments]); // [1,2]
}
func(1, 2);
对象展开运算符:
1、对象展开运算符的基本用法:
(1)展开对象:
对象不能直接展开,必须在 {} 中展开。对象的展开:把属性罗列出来,用逗号分隔,放到一个 {} 中,构成新对象。
const apple = {
color: '红色',
shape: '球形',
taste: '甜'
};
console.log(...apple); // 错误的写法!
console.log([...apple]); // 错误的写法!
console.log({ ...apple }); // {color: "红色", shape: "球形", taste: "甜"}
// 这里展开的是一个新对象,与之前的对象不一致
console.log({ ...apple } === apple); // false
(2)合并对象:
新对象拥有全部属性,相同属性,后者覆盖前者。
const apple = {
color: '红色',
shape: '球形',
taste: '甜'
};
const pen = {
color: '黑色',
shape: '圆柱形',
use: '写字'
};
// 新对象拥有全部属性,相同属性,后者覆盖前者。
console.log({ ...apple, ...pen }); // {color: "黑色", shape: "圆柱形", taste: "甜", use: "写字"}
console.log({ ...pen, ...apple }); // {color: "红色", shape: "球形", use: "写字", taste: "甜"}
2、对象展开运算符的注意事项:
(1)空对象的展开:
如果展开的是一个空对象,则没有任何效果。
console.log({ ...{} }); // {}
console.log({ ...{}, a: 1 }); // {a:1}
(2)非对象的展开:
如果展开的不是对象,则会自动将其转为对象,再将其属性罗列出来。
console.log({ ...1 }); // {}
console.log({ ...undefined }); // {}
console.log({ ...null }); // {}
console.log({ ...true }); // {}
如果展开运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
// 字符串在对象种展开:
console.log({ ...'alex' }); // {0: "a", 1: "l", 2: "e", 3: "x"}
// 对比字符串在数组中展开:
console.log([...'alex']); // (4) ["a", "l", "e", "x"]
// 字符串直接展开:
console.log(...'alex'); // a l e x
// 在对象中展开数组:
console.log(...[1,2,3]); // {0: 1, 1: 2, 2: 3}
(3)对象中对象属性的展开:
不会展开对象中的对象属性。
const apple = {
feature: {
taste: '甜'
}
};
console.log({ ...apple }); // {feature: {…}}
还有,在对象合并时,如果里面有相同的对象属性,则对象属性里面的属性不会合并,后者会覆盖前者。这里举个例子更好说明白:
const apple = {
feature: {
taste: '甜'
}
};
const pen = {
feature: {
color: '黑色',
shape: '圆柱形'
},
use: '写字'
};
console.log({ ...apple, ...pen }); // feature:color: "黑色",shape: "圆柱形"
// feature里面没有taste,apple里面有feature,pen里面也有feature,因此后面的feature会覆盖前面的feature,强烈建议这点大家自己敲一遍试试。