JavaScript 中 ... 前缀的三种核心用法详解
本文详细解析 JavaScript 中 ... 前缀的三种主要用法:函数剩余参数、展开语法和解构赋值。通过对比 arguments 对象,说明剩余参数的优势;深入探讨展开语法在函数调用、数组及对象构造中的应用,并指出其浅拷贝特性;最后介绍数组解构中的剩余模式。
自 ES6 起,JavaScript 引入了剩余参数语法和展开语法等新特性。这些特性均以 ... 符号为前缀,看似功能相似且应用广泛,实则存在本质区别,常令初学者困惑。本文旨在归纳并解析目前常见的三种关于 ... 符号的具体用法。
第一种用法:函数的剩余参数 (Rest parameters)
在定义函数时,若最后一个形参(theArgs)以 ... 为前缀,则该形参(theArgs)将变为数组。调用函数时,若实参数量大于等于形参数量(如例 1.1 中定义 3 个形参却传入 5 个实参),从第 3 个开始(含)的实参均会被归入 theArgs 中:
// 例 1.1
function fn(a, b, ...theArgs) {
console.log(theArgs)
}
fn(1, 2, 3, 4, 5) // [ 3, 4, 5 ]
需要注意的是,...theArgs 必须作为函数的最后一个形参,否则将引发语法错误:“SyntaxError: Rest parameter must be last formal parameter”。在非箭头函数中,可以通过 arguments 对象获取传递给函数的所有参数。那么,剩余参数与 arguments 对象有何区别呢?
- 首先,剩余参数(如例 1.1 中的
theArgs)是一个真正的数组,可以直接调用数组原型上的方法;而 arguments 是一个类数组(array-like)对象,若需使用数组方法,必须先将其转换为数组。 - 其次,剩余参数仅包含那些没有对应显式形参的多余实参,例如例 1.1 中的 3、4 和 5;而 arguments 对象则包含传递给函数的所有实参。
- 此外,arguments 对象拥有 callee 属性,可用于获取当前 arguments 所在的函数引用。
arguments 是早期 JavaScript 规范中为了方便获取函数参数而设计的机制。在 ES6 之后,建议优先使用剩余参数语法,以替代传统的 arguments 用法,从而获得更清晰、更现代的代码风格。
第二种用法:展开语法 (Spread syntax)
展开语法主要适用于以下三个场景:
1)在函数调用时
剩余参数用于函数定义时的形参列表,而展开语法则常用于函数调用时的实参传递。其作用是将数组表达式或字符串在语法层面展开,作为独立的实参传递给函数。例如以下代码:
// 例 2.1
const arr = [1, 2, 3]
const string = 'Jay'
function fn(a, b, c) {
console.log(a, b, c)
}
假设 fn 函数接收 3 个参数,若希望将数组 arr 中的每个元素作为独立参数传递给 fn,在 ES6 之前通常借助 apply 方法实现:
// 例 2.1.1
fn.apply(null, arr) // 1 2 3
引入展开语法后,可以直接将参数数组 arr 展开,从而显著提升代码的可读性:
// 例 2.1.2
fn(...arr) // 1 2 3
此外,展开语法还可用于将字符串展开为字符序列:
// 例 2.1.3
fn(...string) // J a y
2)在构造数组时
在使用字面量方式创建数组时,同样可以使用展开语法:
// 例 2.2
const arr1 = [1, 2]
const arr2 = [3, 4, 5]
// 构造字面量数组
const newArr = [...arr1, ...arr2]
console.log(newArr) // [ 1, 2, 3, 4, 5 ]
3)在构造字面量对象时
除了数组,ES2018 (ES9) 引入了新特性,允许在构造对象时使用展开语法。被展开的对象表达式将按键值对 (key-value) 的方式展开:
// 例 2.3.1
const obj = { name: 'Jay' }
const newObj = { ...obj, age: 40 }
console.log(newObj) // { name: 'Jay', age: 40 }
在构造对象字面量时,还可以将数组放入其中进行展开。例如 MDN 文档中关于展开语法的说明包含如下示例:
// 例 2.3.2
const obj1 = { foo: 'bar', x: 42 }
const obj2 = { foo: 'baz', y: 13 }
const merge = (...objects) => ({ ...objects })
const mergedObj = merge(obj1, obj2)
console.log(mergedObj) // { '0': { foo: 'bar', x: 42 }, '1': { foo: 'baz', y: 13 } }
这段代码初看可能令人困惑,难点主要在于第 4 行。该行定义了一个函数 const merge = (...objects) => ({ ...objects },其中 => 左侧的 ...objects 使用了剩余参数语法,因此 objects 是一个由 obj1 和 obj2 两个对象组成的数组:[ { foo: 'bar', x: 42 }, { foo: 'baz', y: 13 } ];而 => 右侧的 ...objects 使用了展开语法,用于构造字面量对象。由于 objects 是一个数组,展开数组后生成的对象的键名即为数组的下标,因此 { ...objects } 最终得到的对象为:{ '0': { foo: 'bar', x: 42 }, '1': { foo: 'baz', y: 13 } }。
注意事项
在调用函数或构造数组时使用展开语法,操作对象必须是可迭代对象。例如,不能直接在构造数组时将一个普通对象放入展开语法中使用。
展开语法本质上是一种浅拷贝操作
在 MDN 文档中可以看到如下说明:
实际上,展开语法与 Object.assign() 的行为一致,执行的都是浅拷贝(仅遍历一层属性)。
这意味着如例 2.4 所示的情况:
// 例 2.4
const obj = {
a: 'a',
b: {
c: 'd'
}
}
const newObj = { ...obj }
newObj.b.c = '我变了'
console.log(obj.b.c) // 我变了
当我们修改通过展开语法生成的 newObj 的深层属性 newObj.b.c = '我变了' 时,会导致原始对象 obj.b.c 的值同步发生变化。这一现象可以通过内存模型图来解释:

语句 const newObj = { ...obj } 生成的 newObj,实际上是将堆内存中 obj 指向的地址(如 0x100)所指向的对象整体复制了一份,生成了地址为 0x300 的新对象(注意,该新对象中 b 属性的值仍然是一个内存地址)。随后让 newObj 指向这个新地址。如果我们修改 newObj 的 a 属性或直接修改 b 属性的值(即直接改变 b 指向的地址),当然不会影响原始 obj 对象;但是,如果我们修改的是 newObj.b.c,程序会找到地址为 0x200 的对象并进行修改,这自然会影响到原始 obj 对象。
第三种用法:解构数组时
在对数组进行解构赋值时,我们可以使用剩余模式,将剩余的数组元素赋值给一个变量,其效果类似于函数的剩余参数。例如例 3.1 中的 c:
// 例 3.1
const arr = [1, 2, 3, 4, 5]
const [a, b, ...c] = arr
console.log(c) // [ 3, 4, 5 ]以上就是对 JavaScript 中以 ... 为前缀的几种主要用法的详细解析。如需了解更多关于 JS ... 前缀用法的资料,请持续关注本站的其他相关文章!
JS大数值转字符串出现科学计数法怎么办
2026-11-05 17:09:21
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
