剩余参数(Rest Parameters)和展开运算符(Spread Operator)是 ES6 引入的语法特性,使用相同的 ... 语法,但作用相反。
剩余参数允许将不定数量的参数表示为数组。
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
特点:
// 正确:剩余参数在最后
function fn(a, b, ...rest) {
console.log(a, b, rest);
}
// 错误:剩余参数不能在中间
// function fn(...rest, a) { } // SyntaxError
// 剩余参数是数组
function logArgs(...args) {
console.log(Array.isArray(args)); // true
args.forEach(arg => console.log(arg));
}
展开运算符可以将数组或对象展开为多个元素。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// 合并数组
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 在数组中间展开
const arr3 = [0, ...arr1, 4];
console.log(arr3); // [0, 1, 2, 3, 4]
// 复制数组
const copy = [...arr1];
console.log(copy); // [1, 2, 3]
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
// 与普通参数混合
console.log(sum(0, ...numbers)); // 6 (0, 1, 2)
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
// 合并对象
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
// 复制对象
const copy = { ...obj1 };
console.log(copy); // { a: 1, b: 2 }
// 覆盖属性
const updated = { ...obj1, b: 3 };
console.log(updated); // { a: 1, b: 3 }
// 处理不定参数
function formatMessage(template, ...values) {
return template.replace(/{(\d+)}/g, (match, index) => {
return values[index] || match;
});
}
console.log(formatMessage('Hello {0}, you have {1} messages', 'Alice', 5));
// 'Hello Alice, you have 5 messages'
// 参数默认值
function createUser(name, age = 18, ...hobbies) {
return {
name,
age,
hobbies
};
}
const user = createUser('Alice', 25, 'reading', 'coding', 'music');
console.log(user);
// { name: 'Alice', age: 25, hobbies: ['reading', 'coding', 'music'] }
// 添加元素
const arr = [1, 2, 3];
const newArr = [...arr, 4, 5];
console.log(newArr); // [1, 2, 3, 4, 5]
// 插入元素
const inserted = [...arr.slice(0, 1), 1.5, ...arr.slice(1)];
console.log(inserted); // [1, 1.5, 2, 3]
// 删除元素(配合 filter)
const removed = [...arr.filter(x => x !== 2)];
console.log(removed); // [1, 3]
// 数组去重
const duplicates = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(duplicates)];
console.log(unique); // [1, 2, 3]
// 添加属性
const user = { name: 'Alice' };
const updated = { ...user, age: 25 };
console.log(updated); // { name: 'Alice', age: 25 }
// 更新属性
const user2 = { name: 'Alice', age: 25 };
const updated2 = { ...user2, age: 26 };
console.log(updated2); // { name: 'Alice', age: 26 }
// 删除属性(配合解构)
const { age, ...withoutAge } = { name: 'Alice', age: 25, city: 'NYC' };
console.log(withoutAge); // { name: 'Alice', city: 'NYC' }
// 合并对象(后面的覆盖前面的)
const defaults = { theme: 'light', lang: 'en' };
const userPrefs = { theme: 'dark' };
const config = { ...defaults, ...userPrefs };
console.log(config); // { theme: 'dark', lang: 'en' }
// 组合函数
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
const addOne = x => x + 1;
const multiplyTwo = x => x * 2;
const square = x => x * x;
const composed = compose(square, multiplyTwo, addOne);
console.log(composed(3)); // ((3 + 1) * 2) ^ 2 = 64
// 使用展开运算符调用
const result = compose(...[square, multiplyTwo, addOne])(3);
console.log(result); // 64
// 将类数组转换为数组
function test() {
const args = [...arguments];
console.log(Array.isArray(args)); // true
}
test(1, 2, 3);
// NodeList 转数组
const divs = document.querySelectorAll('div');
const divArray = [...divs];
console.log(Array.isArray(divArray)); // true
// 数组浅拷贝
const original = [1, 2, { a: 3 }];
const copy = [...original];
copy[0] = 10;
copy[2].a = 30;
console.log(original); // [1, 2, { a: 30 }] (对象是引用)
// 对象浅拷贝
const obj = { a: 1, b: { c: 2 } };
const objCopy = { ...obj };
objCopy.a = 10;
objCopy.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } } (嵌套对象是引用)
// 条件添加属性
const condition = true;
const obj = {
name: 'Alice',
...(condition && { age: 25 })
};
console.log(obj); // { name: 'Alice', age: 25 }
// 条件添加数组元素
const includeExtra = true;
const arr = [
1, 2, 3,
...(includeExtra ? [4, 5] : [])
];
console.log(arr); // [1, 2, 3, 4, 5]
// 剩余元素
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// 剩余属性
const { name, ...others } = { name: 'Alice', age: 25, city: 'NYC' };
console.log(name); // 'Alice'
console.log(others); // { age: 25, city: 'NYC' }
const nested = { a: { b: 1 } };
const copy = { ...nested };
copy.a.b = 2;
console.log(nested.a.b); // 2 (被修改了)
// 数组和字符串可以展开
console.log(...[1, 2, 3]); // 1 2 3
console.log(...'hello'); // h e l l o
// 对象不能直接展开(ES2018+ 可以)
const obj = { a: 1, b: 2 };
// console.log(...obj); // TypeError (ES2017)
console.log({ ...obj }); // { a: 1, b: 2 } (ES2018+)
// 正确
function fn(a, ...rest) { }
// 错误
// function fn(...rest, a) { } // SyntaxError
// 展开大数组可能影响性能
const largeArray = new Array(1000000).fill(0);
// const spread = [...largeArray]; // 可能较慢
function createElement(tag, props, ...children) {
return {
tag,
props: props || {},
children
};
}
const element = createElement('div', { id: 'app' }, 'Hello', 'World');
function wrapper(...args) {
console.log('Before');
originalFunction(...args);
console.log('After');
}
function createConfig(userConfig) {
const defaultConfig = {
timeout: 1000,
retries: 3
};
return { ...defaultConfig, ...userConfig };
}