1. 扩展运算符与Rest参数的本质区别
作为一名经历过无数次代码调试的前端老兵,我见过太多新人被这三个点(...)折磨得死去活来。这玩意儿就像瑞士军刀,用好了能让你代码优雅如诗,用错了直接让你debug到怀疑人生。
1.1 语法位置的生死判决
记住这个铁律:位置决定身份。同样的三个点,放在不同位置就是完全不同的两个东西:
javascript复制// 案例1:函数定义时 -> Rest参数(收集)
function collect(...items) {
console.log(items) // 数组形式收集所有参数
}
// 案例2:函数调用时 -> 扩展运算符(展开)
const numbers = [1, 2, 3]
console.log(...numbers) // 相当于 console.log(1, 2, 3)
关键区别:Rest参数是在函数定义阶段收集参数,扩展运算符是在函数调用阶段展开数据。就像吸尘器(收集)和喷雾器(扩散)的区别。
1.2 底层原理剖析
在V8引擎内部,这两种用法会被编译成完全不同的字节码:
- Rest参数会创建一个Arguments对象并转换为真实数组
- 扩展运算符会调用迭代器接口(Symbol.iterator)进行展开
javascript复制// 伪代码展示内部处理差异
function restExample(...args) {
// 编译后相当于:
const args = Array.from(arguments)
}
const arr = [1, 2, 3]
// 编译后相当于:
const expanded = arr[Symbol.iterator]().toArray()
2. 扩展运算符的六大实战场景
2.1 数组的优雅操作
2.1.1 安全克隆数组
javascript复制const original = [1, 2, {name: 'obj'}]
const clone = [...original] // 一级深拷贝
// 注意陷阱!
clone[2].name = 'changed' // 会影响原数组的引用对象
2.1.2 数组合并新姿势
javascript复制const frontend = ['React', 'Vue']
const backend = ['Node', 'Express']
const fullstack = [...frontend, 'TypeScript', ...backend]
2.1.3 替代apply的现代写法
javascript复制// 旧时代写法
Math.max.apply(null, [1, 2, 3])
// 现代写法
Math.max(...[1, 2, 3])
2.2 对象的进阶技巧
2.2.1 对象合并的优先级规则
javascript复制const defaults = { color: 'red', size: 'md' }
const user = { color: 'blue', darkMode: true }
// 后者覆盖前者
const config = { ...defaults, ...user }
// { color: 'blue', size: 'md', darkMode: true }
2.2.2 Symbol属性的特殊处理
javascript复制const id = Symbol('id')
const obj = { [id]: 123, name: 'test' }
const clone = { ...obj }
console.log(clone[id]) // 123 Symbol属性会被保留
3. Rest参数的四大神技
3.1 函数参数的灵活处理
3.1.1 动态参数处理
javascript复制function dynamicParams(first, ...rest) {
console.log(`第一个参数:${first}`)
console.log(`剩余参数:${rest.join(', ')}`)
}
dynamicParams(1, 2, 3, 4)
3.1.2 替代arguments对象
javascript复制// 传统方式
function oldSchool() {
const args = Array.from(arguments) // 需要转换
}
// 现代方式
function modern(...args) {
// args已经是真数组
}
3.2 解构赋值的妙用
3.2.1 数组解构剩余项
javascript复制const [first, second, ...others] = [1, 2, 3, 4, 5]
console.log(others) // [3, 4, 5]
3.2.2 对象属性过滤
javascript复制const user = {
id: 1,
password: 'secret',
name: 'Alice',
token: 'xyz'
}
const { password, token, ...publicData } = user
// publicData = { id: 1, name: 'Alice' }
4. 深度踩坑指南
4.1 浅拷贝引发的血案
javascript复制const original = {
config: {
darkMode: true
}
}
const copy = { ...original }
copy.config.darkMode = false // 原对象也被修改!
// 解决方案:
const deepCopy = JSON.parse(JSON.stringify(original)) // 方法1
import { cloneDeep } from 'lodash' // 方法2
4.2 浏览器兼容性雷区
| 特性 | Chrome | Firefox | Safari | Edge | IE |
|---|---|---|---|---|---|
| 数组扩展运算符 | 46+ | 16+ | 8+ | 12+ | ❌ |
| 对象扩展运算符 | 60+ | 55+ | 11.1+ | 79+ | ❌ |
| Rest参数 | 47+ | 15+ | 10+ | 12+ | ❌ |
生产环境必须配置Babel的@babel/plugin-proposal-object-rest-spread插件
5. 性能优化实战
5.1 大数据量处理对比
javascript复制// 不推荐的写法(每次扩展都创建新数组)
const bigData = [...Array(100000).keys()]
function badExample(items) {
return [...items, ...bigData] // 内存爆炸!
}
// 优化方案1:使用concat
function goodExample(items) {
return items.concat(bigData)
}
// 优化方案2:使用push.apply
function betterExample(items) {
Array.prototype.push.apply(items, bigData)
return items
}
5.2 React性能陷阱
jsx复制// 不推荐的props传递方式
function Component(props) {
return <Child {...props} extra="value" />
}
// 优化写法:明确传递必要属性
function OptimizedComponent({ id, name, ...rest }) {
return <Child id={id} name={name} extra="value" />
}
6. 高级技巧揭秘
6.1 函数式编程组合
javascript复制// 管道函数实现
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)
const add5 = x => x + 5
const double = x => x * 2
const square = x => x * x
const transform = pipe(add5, double, square)
console.log(transform(2)) // ((2 + 5) * 2)^2 = 196
6.2 条件属性展开
javascript复制const baseStyle = { color: 'black' }
const isError = true
const buttonStyle = {
...baseStyle,
...(isError && { color: 'red' }) // 条件展开
}
7. 面试高频问题解析
7.1 经典面试题:实现merge函数
javascript复制function merge(...objects) {
return objects.reduce((acc, obj) => {
for (const [key, value] of Object.entries(obj)) {
acc[key] = value
}
return acc
}, {})
}
// 测试用例
console.log(merge({a:1}, {b:2}, {a:3})) // {a:3, b:2}
7.2 手写浅拷贝实现
javascript复制function shallowClone(obj) {
if (Array.isArray(obj)) {
return [...obj]
} else if (typeof obj === 'object' && obj !== null) {
return { ...obj }
}
return obj
}
8. 实际项目应用案例
8.1 API请求参数处理
javascript复制async function fetchData(baseURL, endpoint, { headers, ...options }) {
const defaultHeaders = {
'Content-Type': 'application/json',
'Authorization': 'Bearer xxx'
}
const response = await fetch(`${baseURL}${endpoint}`, {
...options,
headers: {
...defaultHeaders,
...headers
}
})
return response.json()
}
8.2 Redux reducer编写
javascript复制function todosReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id
? { ...todo, completed: !todo.completed }
: todo
)
default:
return state
}
}
9. TypeScript中的增强类型
9.1 类型安全的Rest参数
typescript复制function typedRest<T extends any[]>(...args: T): T {
return args
}
const result = typedRest(1, '2', true) // 类型推断为 [number, string, boolean]
9.2 扩展运算符的类型展开
typescript复制interface Base {
id: number
createdAt: Date
}
interface User extends Base {
name: string
email: string
}
function updateUser<T extends Base>(original: T, changes: Partial<T>) {
return { ...original, ...changes }
}
const user: User = { id: 1, createdAt: new Date(), name: 'Alice', email: 'a@b.com' }
const updated = updateUser(user, { name: 'Bob' }) // 类型安全!
10. 终极测试:你能答对几题?
10.1 诡异的结果预测
javascript复制const obj = { a: 1, b: 2 }
const arr = [...obj] // 会发生什么?
10.2 嵌套展开的陷阱
javascript复制const x = [1, 2, 3]
const y = [4, 5]
const z = [...x, ...y] // 结果是什么?
const w = [...x, y] // 这个呢?
10.3 Rest参数的边界情况
javascript复制function test(a, b, ...c) {
console.log(c.length)
}
test(1) // ?
test(1, 2) // ?
test(1, 2, 3) // ?
test(1, 2, 3, 4) // ?
经过这10个章节的系统梳理,相信你已经从"傻傻分不清"进阶到"运用自如"的境界。记住,真正的掌握不在于死记硬背,而在于不断实践和思考。下次在代码中看到这三个点时,希望你能会心一笑:"小样儿,我早就看透你了!"