js ES6扩展运算符(spread)和剩余运算符(rest)

本文深入探讨了JavaScript中的扩展运算符(spread)和剩余运算符(rest)的语法、特性及应用场景,包括数组和对象的操作、函数参数的处理等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、扩展运算符(spread)

1、扩展运算符的语法

2、扩展运算符特性

3、扩展运算符 与 表达式

4、扩展运算符 与 对象(Object)

(1)、复制对象

(2)、合并对象

(3)、将数组转为对象

(4)、空对象处理的几种情况 与 自动转为空对象的几种情况

(5)、自定义的属性的前置与后置的区别

5、扩展运算符 与 数组

(1)、复制数组(浅拷贝)

(2)、交换变量的值

(3)、合并数组

(4)、将一个数组插入到另一个数组中

(5)、如果扩展运算符后面是一个空数组,有以下三种情况:

(6)、将可迭代对象转为数组

6、扩展运算符 与 函数

(1)、扩展运算符(spread)与剩余运算符(rest)在函数中使用的区别

(2)、扩展运算符的参数对象之中的取值方法会立即执行

(3)、用扩展运算符解构“可迭代对象”作为函数的实参

二、剩余运算符(rest)

1、rest 运算符的语法

2、rest 运算符的特性

(1)、rest 参数只能作为函数的最后一个参数

(2)、rest 参数搭配的变量是一个数组

(3)、函数的 length 属性不包括 rest 参数

(4)、rest 参数取代了 arguments 对象

(5)、使用了 rest 参数的函数内部不能显示设定严格模式,否则会报错

3、rest 运算符的用途

(1)、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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值