JavaScript 展开语法
什么是展开语法?
展开语法(Spread Syntax)是JavaScript ES6(ECMAScript 2015)引入的一个强大特性,用三个点(...
)表示。它允许我们在需要多个元素的位置(如数组字面量、函数调用等)将一个可迭代对象"展开"为多个独立的值。
简单来说,展开语法可以让我们以更简洁、更直观的方式处理数组和对象。
提示
展开语法(...
)可以让代码更简洁,避免使用一些传统的方法,如apply()
、concat()
等来合并或复制数组和对象。
展开语法在数组中的应用
合并数组
使用展开语法可以轻松地将多个数组合并成一个新数组:
javascript
const fruits = ['苹果', '香蕉'];
const vegetables = ['胡萝卜', '西兰花'];
// 使用展开语法合并数组
const food = [...fruits, ...vegetables];
console.log(food); // 输出: ['苹果', '香蕉', '胡萝卜', '西兰花']
// 还可以在合并时添加新元素
const moreFood = [...fruits, '橙子', ...vegetables];
console.log(moreFood); // 输出: ['苹果', '香蕉', '橙子', '胡萝卜', '西兰花']
复制数组
展开语法可以创建数组的浅拷贝(shallow copy):
javascript
const originalArray = [1, 2, 3];
const copyArray = [...originalArray];
console.log(copyArray); // 输出: [1, 2, 3]
copyArray.push(4);
console.log(originalArray); // 输出: [1, 2, 3] (原数组不受影响)
console.log(copyArray); // 输出: [1, 2, 3, 4]
警告
展开语法创建的是浅拷贝,意味着如果数组包含对象或嵌套数组,这些内部对象仍然是引用关系,而不是完全独立的副本。
将类数组对象转换为数组
DOM操作中,我们经常会遇到类数组对象,如NodeList
。展开语法可以将它们轻松转换为真正的数组:
javascript
// 假设页面上有多个段落元素
const paragraphs = document.querySelectorAll('p');
console.log(paragraphs); // NodeList对象
// 转换为真正的数组
const paragraphsArray = [...paragraphs];
console.log(paragraphsArray); // 数组,可以使用所有数组方法
paragraphsArray.forEach(p => p.style.color = 'blue');
在函数调用中使用展开语法
可以使用展开语法将数组元素作为单独的参数传递给函数:
javascript
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
// 使用展开语法调用函数
console.log(sum(...numbers)); // 输出: 6
// 等同于
console.log(sum(1, 2, 3)); // 输出: 6
展开语法在对象中的应用
ES2018(ES9)扩展了展开语法,使其可以用于对象字面量。
合并对象
javascript
const person = { name: '张三', age: 25 };
const job = { title: '开发者', company: 'Tech Co.' };
// 合并对象
const personWithJob = { ...person, ...job };
console.log(personWithJob);
// 输出: { name: '张三', age: 25, title: '开发者', company: 'Tech Co.' }
对象的浅拷贝
javascript
const original = { a: 1, b: 2, c: { deep: 'value' } };
const copy = { ...original };
console.log(copy); // 输出: { a: 1, b: 2, c: { deep: 'value' } }
// 修改复制后的对象不会影响原对象的简单属性
copy.a = 5;
console.log(original.a); // 输出: 1
console.log(copy.a); // 输出: 5
// 但对于嵌套对象,因为是浅拷贝,修改会影响原对象
copy.c.deep = 'new value';
console.log(original.c.deep); // 输出: 'new value'
console.log(copy.c.deep); // 输出: 'new value'
使用展开语法添加或覆盖属性
javascript
const baseConfig = {
api: 'https://api.example.com',
timeout: 5000,
debug: false
};
// 创建一个新配置,覆盖timeout属性并添加新属性
const developmentConfig = {
...baseConfig,
timeout: 0, // 覆盖timeout
debug: true, // 覆盖debug
env: 'development' // 添加新属性
};
console.log(developmentConfig);
/* 输出:
{
api: 'https://api.example.com',
timeout: 0,
debug: true,
env: 'development'
}
*/
备注
如果展开多个对象时有相同的属性,后面对象的属性值会覆盖前面的。
实际应用场景
场景1:React中传递props
在React中,展开语法常用于传递props:
jsx
function ParentComponent() {
const userProps = {
name: '张三',
email: 'zhang@example.com',
role: 'admin'
};
return (
<UserProfile
{...userProps}
isOnline={true}
/>
);
}
场景2:函数参数默认值与配置合并
javascript
function createUser(userData = {}) {
// 默认配置
const defaultConfig = {
role: 'user',
permissions: ['read'],
active: true
};
// 合并默认配置和用户提供的配置
const finalConfig = { ...defaultConfig, ...userData };
return finalConfig;
}
// 使用
const admin = createUser({ role: 'admin', permissions: ['read', 'write', 'delete'] });
console.log(admin);
/* 输出:
{
role: 'admin',
permissions: ['read', 'write', 'delete'],
active: true
}
*/
场景3:不可变数据处理
在函数式编程中,展开语法有助于创建不可变数据更新:
javascript
const gameState = {
player: { x: 100, y: 200, health: 80 },
enemies: [
{ id: 1, x: 70, y: 50 },
{ id: 2, x: 200, y: 75 }
],
score: 450
};
// 更新玩家健康值的不可变更新
const newGameState = {
...gameState,
player: {
...gameState.player,
health: gameState.player.health + 20
},
score: gameState.score + 50
};
console.log(newGameState.player.health); // 100
console.log(gameState.player.health); // 80 (原对象未被修改)
场景4:构建查询参数
构建URL查询参数时,展开语法很有用:
javascript
function buildApiUrl(baseUrl, params = {}) {
// 默认参数
const defaultParams = {
format: 'json',
version: 'v1'
};
// 合并参数
const queryParams = { ...defaultParams, ...params };
// 构建查询字符串
const queryString = Object.entries(queryParams)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
return `${baseUrl}?${queryString}`;
}
const url = buildApiUrl('https://api.example.com/data', {
limit: 10,
offset: 20,
format: 'xml' // 覆盖默认format
});
console.log(url);
// 输出: https://api.example.com/data?format=xml&version=v1&limit=10&offset=20
展开语法与剩余参数
虽然展开语法和剩余参数使用相同的...
符号,但它们的用途不同:
- 展开语法(Spread): 将一个可迭代对象展开为多个独立的值
- 剩余参数(Rest): 将多个独立的值收集到一个数组中
javascript
// 剩余参数示例 - 收集多余的参数到一个数组
function collectArgs(first, second, ...rest) {
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
}
collectArgs(1, 2, 3, 4, 5);
// 展开语法 - 将数组展开为独立参数
const numbers = [1, 2, 3, 4, 5];
collectArgs(...numbers);
总结
展开语法是JavaScript中一个强大而简洁的特性,可以帮助我们:
- 合并或复制数组和对象
- 将类数组对象转换为数组
- 将数组元素作为独立参数传递给函数
- 创建对象的浅拷贝
- 以不可变的方式更新对象或数组
虽然展开语法提供了很多便利,但使用时需要注意它创建的是浅拷贝,如果需要深拷贝,可能需要其他解决方案,如JSON.parse(JSON.stringify())
或深拷贝库。
练习
- 使用展开语法合并两个数组
[1, 2, 3]
和[4, 5, 6]
,并在中间添加数字0
。 - 创建一个函数,接受任意数量的数字参数并返回它们的平均值。
- 使用展开语法为一个基础对象添加新属性,同时不修改原对象。
- 实现一个
deepMerge
函数,使用展开语法合并两个嵌套对象。