跳到主要内容

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中一个强大而简洁的特性,可以帮助我们:

  1. 合并或复制数组和对象
  2. 将类数组对象转换为数组
  3. 将数组元素作为独立参数传递给函数
  4. 创建对象的浅拷贝
  5. 以不可变的方式更新对象或数组

虽然展开语法提供了很多便利,但使用时需要注意它创建的是浅拷贝,如果需要深拷贝,可能需要其他解决方案,如JSON.parse(JSON.stringify())或深拷贝库。

练习

  1. 使用展开语法合并两个数组[1, 2, 3][4, 5, 6],并在中间添加数字0
  2. 创建一个函数,接受任意数量的数字参数并返回它们的平均值。
  3. 使用展开语法为一个基础对象添加新属性,同时不修改原对象。
  4. 实现一个deepMerge函数,使用展开语法合并两个嵌套对象。

延伸阅读