目录
(1)、扩展运算符(spread)与剩余运算符(rest)在函数中使用的区别
(5)、使用了 rest 参数的函数内部不能显示设定严格模式,否则会报错
(2)、当函数参数个数不确定时,用 rest 参数作为函数的形参
一、扩展运算符(spread)
1、扩展运算符的语法
扩展运算符用三个点号表示(...),用来:
- 在 函数调用 / 数组构造 时,将 数组表达式 或者 string 在语法层面展开;
- 在 构造字面量对象 时,将对象表达式按 key-value 的方式展开。
2、扩展运算符特性
- 扩展运算符后面 可以放置表达式。
- 在 构造字面量对象 时,用来将对象表达式按 key-value 的方式展开,但是,不能超过 JavaScript 引擎限制的最大参数个数。
- 在 数组 或 函数参数 中使用 展开语法(...)时,能且只能用来将可迭代对象(部署了 Iterator 遍历器)的数组表达式 或者 string 在语法层面展开。
- 任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。常见的 可迭代对象 包括:
- Array
- Map
- Set
- String
- 函数中的 arguments 对象
- NodeList 对象
- Generator 函数
3、扩展运算符 与 表达式
扩展运算符后面 可以放置表达式。
var x = 10;
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];
const obj = {
...(x > 1 ? {a: 1} : {}),
b: 2,
};
console.log(arr);// ["a", "b"]
console.log(obj);// {a: 1, b: 2}
4、扩展运算符 与 对象(Object)
(1)、复制对象
使用扩展运算符克隆对象时,若对象只有一层则是深拷贝,若对象有多层则是浅拷贝(不包含 prototype),详情请戳这里。
var obj1 = { foo: 'bar', x: 42, obj2:{ q: 'qq', p: 'pp' }};
{...obj1};
// {foo: "bar", x: 42, obj2: {…}}
// foo: "bar"
// obj2: {q: "qq", p: "pp"}
// x: 42
// __proto__: Object
扩展运算符 与 Object.assign() 的区别:
- Object.assign() 函数会触发 setters,而展开语法则不会。
- 不能用 展开语法 替换或者模拟 Object.assign() 函数。
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
const merge = ( ...objects ) => ( { ...objects } );
var mergedObj = merge ( obj1, obj2);
// Object { 0: { foo: 'bar', x: 42 }, 1: { foo: 'baz', y: 13 } }
var mergedObj = merge ( {}, obj1, obj2);
// Object { 0: {}, 1: { foo: 'bar', x: 42 }, 2: { foo: 'baz', y: 13 } }
在上述代码中,展开操作符并没有按预期的方式执行:而是先将多个解构变为剩余参数,然后再将剩余参数展开为字面量对象。
(2)、合并对象
let a = {a:"111"};
let b = {b:"222"};
console.log({ ...a, ...b });// {a: "111", b: "222"}
// 等同于
let ab = Object.assign({}, a, b);
(3)、将数组转为对象
数组是特殊的对象,可以用扩展运算符直接将数组转为对象。
console.log({...[1, 2, 3]});
// {0: 1, 1: 2, 2: 3}
(4)、空对象处理的几种情况 与 自动转为空对象的几种情况
①、如果扩展运算符后面是一个空对象,有以下三种情况:
console.log(...{});// TypeError: Found non-callable @@iterator
console.log({...{}});// {}
console.log({...{}, a: 1});// {a: 1}
②、如果扩展运算符后面不是对象,则会自动将其转为一个空对象。
console.log({...1}); // {}
console.log({...true});// {}
console.log({...undefined});// {}
console.log({...null});// {}
(5)、自定义的属性的前置与后置的区别
①、如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被 覆盖掉。
let a = {x: 3, y: 6, z: 9}
let b = { ...a, x: 1, y: 2 };// {x: 1, y: 2, z: 9}
// 等同于
let c = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, d = { ...a, x, y };
// 等同于
let e = Object.assign({}, a, { x: 1, y: 2 });
②、如果把自定义属性放在扩展运算符前面,就变成了设置新对象的 默认属性值。
let a = {z: 9}
let b = { x: 1, y: 2, ...a };// {x: 1, y: 2, z: 9}
// 等同于
let c = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let d = Object.assign({ x: 1, y: 2 }, a);
5、扩展运算符 与 数组
(1)、复制数组(浅拷贝)
展开语法和 Object.assign() 行为一致,执行的都是 浅拷贝(只遍历一层):
const a1 = [1, 2];
const a2 = [...a1];
const [...a2] = a1;
如果想对多维数组进行深拷贝,下面的示例就有些问题了:
var a = [[1], [2], [3]];
var b = [...a];
b.shift().shift();
a;// [[], [2], [3]]
// 删除 b 数组中第一个子数组的第一项:1,会改变原来的 a 数组。
(2)、交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
(3)、合并数组
const a1 = [1, 2];
const a2 = [3, 4];
const a3 = [...a1, ...a2];// [1, 2, 3, 4]
(4)、将一个数组插入到另一个数组中
const a1 = [1, 2];
const a2 = [...a1, 3, 4];// [1, 2, 3, 4]
(5)、如果扩展运算符后面是一个空数组,有以下三种情况:
console.log(...[])//
console.log([...[]])// []
console.log([...[], 1])// [1]
(6)、将可迭代对象转为数组
在 数组 中使用 展开语法(...)时,能且只能用来将 可迭代对象(部署了 Iterator 遍历器)的 数组表达式 在语法层面展开。
/* Map 结构 */
let map = new Map();
map.set('key0', 'value0');
map.set('key1', 'value1');
[...map]; // [['key0', 'value0'],['key1', 'value1']]
/* Set 结构 */
let arr = [1, 2, 3];
let set = new Set(arr);
[...set]; // [1, 2, 3]
/* 字符串 */
[...'hello'];// [ "h", "e", "l", "l", "o" ]
/* arguments对象 */
function foo() {
arguments; // Arguments(6) [1, 2, 3, 4, 5, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ]
Array.isArray(arguments); // false
// ES5 中支持这样将arguments转为数组
Array.prototype.slice.call(arguments); // [1, 2, 3, 4, 5, 6]
// ES6 中还能这样将arguments转为数组
[...arguments]; // [1, 2, 3, 4, 5, 6]
}
foo(1, 2, 3, 4, 5, 6);
/* NodeList对象 */
var nodList = document.querySelectorAll('div');
nodList; // NodeList(3) [div, div, div]
Array.isArray(nodList); // false
[...nodList]; // [div, div, div]
由上述代码可见:任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。
6、扩展运算符 与 函数
只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
(...[1, 2]); // Uncaught SyntaxError: Unexpected number
console.log(...[1, 2]); // 1 2
函数参数 中使用 展开语法(...)时,能且只能用来将 可迭代对象(部署了 Iterator 遍历器)的 数组表达式 或者 string 在语法层面展开。
(1)、扩展运算符(spread)与剩余运算符(rest)在函数中使用的区别
扩展运算符(spread)与剩余运算符(rest)虽然都是三个点(...),但它们在函数中的使用完全不同,与 “rest 参数”(下文会讲到)相比:
- 扩展运算符 用于控制传入函数的实参;
- 剩余运算符 用于控制传入函数的形参。
①、扩展运算符 用于控制传入函数的 实参
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
②、剩余运算符 用于控制传入函数的 形参
function fn(...arr){
console.log(arr);
}
fn(1, 2, 3, 4, 5, 6);// [1, 2, 3, 4, 5, 6]
(2)、扩展运算符的参数对象之中的取值方法会立即执行
扩展运算符的参数对象之中,如果有取值函数,这个函数是会执行的。
let a = {
get x() {
console.log("hello world!");
}
}
let aWithXGetter = { ...a }; // "hello world!"
(3)、用扩展运算符解构“可迭代对象”作为函数的实参
function myFunction(x, y, z) {
console.log(arguments);
}
// 数组
var args = [0, 1, 2];
myFunction(...args);// Arguments(3) [0, 1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 字符串
var str = "hello";
myFunction(...str);// Arguments(5) ["h", "e", "l", "l", "o", callee: ƒ, Symbol(Symbol.iterator): ƒ]
// Set
var set = new Set([1,1,2,2]);
console.log(set);// Set(2) {1, 2}
console.log(...set);// 1 2
myFunction(...set);// Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// Map
var map = new Map();
map.set('key0', '111');
map.set('key1', '222');
console.log(map);// Map(2) {"key0" => "111", "key1" => "222"}
console.log(...map);// ["key0", "111"] (2) ["key1", "222"]
myFunction(...map);// Arguments(2) [Array(2), Array(2), callee: ƒ, Symbol(Symbol.iterator): ƒ]
// NodeList对象
var nodList = document.getElementsByClassName("one");
console.log(nodList);// HTMLCollection(3) [div.one, div.one, div.one]
console.log(...nodList);// <div class="one"></div> <div class="one"></div> <div class="one"></div>
myFunction(...nodList);// Arguments(3) [div.one, div.one, div.one, callee: ƒ, Symbol(Symbol.iterator): ƒ]
实际上,用扩展运算符将可迭代对象的元素迭代为 函数实参,等价于用 Function.prototype.apply 的方式,比如:
function myFunction(x, y, z) {
console.log(arguments);
}
var args = [0, 1, 2];
myFunction.apply(null, args);// Arguments(3) [0, 1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
二、剩余运算符(rest)
1、rest 运算符的语法
剩余运算符也是用三个点号表示(...),与扩展运算符相反,剩余运算符用来:将逗号隔开的值序列组合成一个数组或对象。
// 用剩余运算符解构数组
var [a,...temp]=[1, 2, 4];
temp;// [2, 4]
console.log(...temp);// 2 4
// 用剩余运算符解构对象
let { x, ...y } = { x: 1, a: 2, b: 3 };
x // 1
y // { a: 3, b: 3 }
console.log(...y);// TypeError: Found non-callable @@iterator
ES2016 用 rest 运算符 定义了 rest 参数,该参数的形式为 “...变量名” 。该参数主要用于获取函数的多余参数。
2、rest 运算符的特性
- rest 参数之后不能再有其他参数(即只能作为函数的最后一个参数),否则会报错。
- rest 参数搭配的变量是一个数组或对象。
- 函数的 length 属性不包括 rest 参数。
- rest 参数取代了 arguments 对象。
- 使用了 rest 参数的函数内部不能显示设定严格模式,否则会报错。
(1)、rest 参数只能作为函数的最后一个参数
function f(a, ...b, c) {
// ...
}
// SyntaxError: Rest parameter must be last formal parameter
(2)、rest 参数搭配的变量是一个数组
function add(...values) {
console.log(values);
}
add(2, 5, 3) // [2, 5, 3]
(3)、函数的 length 属性不包括 rest 参数
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
(4)、rest 参数取代了 arguments 对象
有了 rest 参数,就不需要使用 arguments 对象了。下面是一个 rest 参数代替 arguments 变量的例子:
// Arguments 写法
function foo1() {
console.log([].slice.call(arguments));
}
foo1(1, 2, 3, 4, 5, 6); // [1, 2, 3, 4, 5, 6]
// rest 写法
function foo2(...rest) {
console.log(rest);
}
foo2(1, 2, 3, 4, 5, 6); // [1, 2, 3, 4, 5, 6]
上述代码中,arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组( “[]” 等价于 “Array.prototype” )。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。 所以 rest 参数更方便实用,完全可以取代 arguments 了。
(5)、使用了 rest 参数的函数内部不能显示设定严格模式,否则会报错
只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
// 报错
const doSomething = (...a) => {
'use strict';
// code
};
其他 “严格模式” 的案列,请戳此链接:js 严格模式_weixin79893765432...的博客-优快云博客
3、rest 运算符的用途
(1)、rest 运算符 配合 解构 使用
// 用于数组
var [a,...temp]=[1, 2, 4];
a;// 1
temp;// [2, 4]
// 用于对象
let { x, ...y } = { x: 1, a: 2, b: 3 };
x // 1
y // { a: 3, b: 3 }
(2)、当函数参数个数不确定时,用 rest 参数作为函数的形参
当函数参数个数不确定时,用 rest 参数 作为函数的形参,会把未来传入函数的参数整合成一个数组。
// 操作数组
function fn(...arr){
console.log(arr);
}
fn(1, 2, 3, 4, 5, 6);
// [1, 2, 3, 4, 5, 6]
// 操作对象
function baseFunction({ a, b }) {
// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
// 使用 x 和 y 参数进行操作
// 其余参数传给原始函数
return baseFunction(restConfig);
}