…的使用
1. Rest Parameters (剩余参数) - 聚集
当 ... 出现在函数的参数列表中时,它就是“剩余参数”。它的作用是捕获函数调用时传入的所有剩余的、未被命名的参数,并将它们放入一个真正的数组中。
核心规则:
- 必须是函数的最后一个参数。
- 一个函数定义中只能有一个剩余参数。
在没有 Rest Parameters 的“远古时代”(ES5)
以前,我们需要使用 arguments 对象来获取所有参数。但 arguments 是一个“类数组对象”,而不是真正的数组,使用起来很麻烦(没有 forEach, map 等方法)。
function sumOld() { // arguments is not a real array, so we have to convert it first const args = Array.prototype.slice.call(arguments);
let total = 0; for (let i = 0; i < args.length; i++) { total += args[i]; } return total;}
console.log(sumOld(1, 2, 3)); // 6使用 Rest Parameters (现代 JS)
代码变得极其简洁和直观。
// ...numbers 会将所有传入的参数聚集到一个名为 `numbers` 的数组中function sum(...numbers) { // `numbers` 是一个真正的数组,可以直接使用数组方法 return numbers.reduce((total, current) => total + current, 0);}
console.log(sum(1, 2, 3)); // 6console.log(sum(10, 20, 30, 40)); // 100console.log(sum()); // 0结合普通参数使用:
function logTeam(captain, coach, ...players) { console.log(`Captain: ${captain}`); console.log(`Coach: ${coach}`); console.log(`Players: ${players.join(', ')}`);}
logTeam("Alice", "Bob", "Charlie", "David", "Eve");// 输出:// Captain: Alice// Coach: Bob// Players: Charlie, David, Eve在这里,players 捕获了除了前两个以外的所有剩余参数。
2. Spread Syntax (展开语法) - 展开
当 ... 出现在函数调用、数组字面量或对象字面量中时,它就是“展开语法”。它的作用是将一个可迭代对象(如数组、字符串)或对象“展开”成其独立的组成部分。
用途一:在函数调用中展开数组
这是 Rest Parameters 的反向操作。
const numbers = [10, 5, 25, 15];
// 以前需要使用 .apply()const max_old = Math.max.apply(null, numbers);
// 现在使用展开语法,非常直观const max = Math.max(...numbers); // 等价于 Math.max(10, 5, 25, 15)
console.log(max); // 25用途二:在数组字面量 [] 中展开
这在处理数组时非常有用,尤其是在**不改变原数组(Immutability)**的情况下。
a) 复制数组(浅拷贝)
const original = ['a', 'b', 'c'];const copy = [...original];
copy.push('d');console.log(original); // ['a', 'b', 'c'] (原数组未受影响)console.log(copy); // ['a', 'b', 'c', 'd']b) 合并数组
const arr1 = [1, 2];const arr2 = [3, 4];const combined = [...arr1, 'middle', ...arr2];
console.log(combined); // [1, 2, 'middle', 3, 4]c) 将字符串转换为字符数组
const str = "hello";const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']用途三:在对象字面量 {} 中展开 (ES2018)
这对于处理对象同样非常强大,是 React 和其他现代框架中状态管理的核心。
a) 复制对象(浅拷贝)
const originalObj = { name: "Alice", age: 30 };const copyObj = { ...originalObj };
console.log(copyObj); // { name: "Alice", age: 30 }b) 合并对象
const obj1 = { a: 1, b: 2 };const obj2 = { b: 3, c: 4 }; // 注意,obj2 也有属性 'b'const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { a: 1, b: 3, c: 4 }// 规则:后面的同名属性会覆盖前面的。c) 以不可变的方式更新对象属性
这是它最常见的用途!在不直接修改原对象的情况下,创建一个包含更新值的新对象。
const user = { id: 101, name: "Bob", profile: { theme: "dark", notifications: true }};
// 更新用户的 nameconst updatedUser = { ...user, name: "Charlie" };console.log(updatedUser); // { id: 101, name: "Charlie", profile: {...} }console.log(user); // { id: 101, name: "Bob", profile: {...} } (原对象未变)
// 更新嵌套对象的属性const userWithNewTheme = { ...user, profile: { ...user.profile, // 先展开老的 profile theme: "light" // 然后用新值覆盖 theme }};console.log(userWithNewTheme.profile.theme); // "light"- 注意:* 无论是数组还是对象,展开语法执行的都是浅拷贝 (Shallow Copy)。这意味着如果对象或数组中包含其他对象(如上面的
profile),那么只复制了对那个嵌套对象的引用,而不是复制嵌套对象本身。
总结
| Rest Parameters (剩余参数) | Spread Syntax (展开语法) | |
|---|---|---|
| 作用 | 聚集 (Gather) | 展开 (Spread) |
| 做什么 | 将多个独立的元素收集成一个数组。 | 将一个数组/对象展开成独立的元素。 |
| 用在何处 | 函数的参数定义中,且必须是最后一个。 | 函数调用时、数组字面量[] 中、对象字面量{} 中。 |
| 示例 | function fn(a, ...rest) {} | const arr = [...oldArr];fn(...args);const obj = {...oldObj}; |
可选链操作符(Optional Chaining ?.)
可选链操作符 ?. 是 ES2020 引入的语法糖,用于安全地访问嵌套对象属性,即使中间某个引用为 null 或 undefined 也不会抛出错误。它是现代 JavaScript 中处理不确定数据结构的重要工具。
核心概念
问题场景: 在访问深层嵌套对象时,如果中间任何一层为 null 或 undefined,就会抛出 TypeError:
// 危险的传统写法const user = null;console.log(user.profile.name); // ❌ TypeError: Cannot read property 'profile' of null
// 或者后端返回的不完整数据const response = { data: { user: null // 用户未登录 }};console.log(response.data.user.profile.name); // ❌ TypeError解决方案: 可选链操作符提供了优雅的短路机制:
const user = null;console.log(user?.profile?.name); // ✅ undefined (不报错)
const response = { data: { user: null }};console.log(response.data.user?.profile?.name); // ✅ undefined三种语法形式
1. 属性访问:obj?.prop
const user = { id: 1, profile: { name: 'Alice', avatar: 'avatar.jpg' }};
// 安全访问存在的属性console.log(user?.profile?.name); // 'Alice'
// 安全访问不存在的属性console.log(user?.settings?.theme); // undefined
// 处理 null/undefined 对象const emptyUser = null;console.log(emptyUser?.profile?.name); // undefined2. 方括号访问:obj?.[expr]
用于动态属性名或需要计算的属性访问:
const config = { development: { apiUrl: 'dev-api.com' }, production: { apiUrl: 'api.com' }};
const env = 'development';console.log(config?.[env]?.apiUrl); // 'dev-api.com'
// 处理不存在的环境配置const invalidEnv = 'testing';console.log(config?.[invalidEnv]?.apiUrl); // undefined
// 与变量结合使用const user = { name: 'Bob', age: 30 };const field = 'email'; // 这个属性不存在console.log(user?.[field]?.toLowerCase()); // undefined3. 方法调用:obj?.method?.()
安全调用可能不存在的方法:
const api = { user: { getName: () => 'Alice', getProfile: null // 方法不存在或为 null }};
// 安全调用存在的方法console.log(api.user?.getName?.()); // 'Alice'
// 安全调用不存在的方法console.log(api.user?.getProfile?.()); // undefined
// 处理整个对象都不存在的情况const emptyApi = null;console.log(emptyApi?.user?.getName?.()); // undefined可选链实际应用场景
1. API 响应处理
// 模拟不稳定的 API 响应const apiResponses = [ { data: { user: { profile: { name: 'John', contact: { email: 'john@email.com' } } } } }, { data: { user: null // 用户未登录 } }, { data: null // 请求失败 }, null // 网络错误];
// 安全处理所有情况apiResponses.forEach((response, index) => { const email = response?.data?.user?.profile?.contact?.email; console.log(`Response ${index}: ${email || 'No email'}`);});
// 输出:// Response 0: john@email.com// Response 1: No email// Response 2: No email// Response 3: No email2. DOM 操作
// 安全访问 DOM 元素const element = document.querySelector('#user-panel');
// 传统写法(容易出错)// if (element && element.querySelector && element.querySelector('.avatar')) {// element.querySelector('.avatar').src = 'new-avatar.jpg';// }
// 可选链写法(简洁安全)const avatar = element?.querySelector?.('.avatar');if (avatar) { avatar.src = 'new-avatar.jpg';}
// 或者更简洁element?.querySelector?.('.avatar')?.setAttribute?.('src', 'new-avatar.jpg');3. 事件处理和回调函数
// 配置对象可能包含可选的回调函数function processData(data, options = {}) { // 处理数据... const result = data.map(item => item.value);
// 安全调用可选的回调函数 options?.onSuccess?.(result); options?.logger?.log?.('Processing completed');
return result;}
// 使用示例processData([{value: 1}, {value: 2}], { onSuccess: (result) => console.log('Success:', result), logger: console // 有 log 方法});
processData([{value: 3}], { // 没有提供回调函数 - 不会报错});4. 配置和设置管理
// 应用配置,某些配置项可能不存在const appConfig = { theme: { primary: '#007bff', // secondary 颜色未配置 }, features: { darkMode: true, // notifications 功能未配置 }};
// 安全获取配置值,提供默认值const primaryColor = appConfig?.theme?.primary ?? '#000000';const secondaryColor = appConfig?.theme?.secondary ?? '#6c757d';const notificationsEnabled = appConfig?.features?.notifications ?? false;
console.log({ primaryColor, // '#007bff' secondaryColor, // '#6c757d' (默认值) notificationsEnabled // false (默认值)});与其他操作符结合
与空值合并操作符 ?? 结合
const user = { profile: { name: null, // 名字为 null bio: '' // 简介为空字符串 }};
// 只有 null/undefined 才使用默认值const name = user?.profile?.name ?? 'Anonymous';const bio = user?.profile?.bio ?? 'No bio available';
console.log(name); // 'Anonymous'console.log(bio); // '' (空字符串不会被替换)
// 如果想把空字符串也替换,使用 ||const bioWithOr = user?.profile?.bio || 'No bio available';console.log(bioWithOr); // 'No bio available'与解构赋值结合
const response = { data: { user: { profile: { name: 'Alice', contact: { email: 'alice@email.com' } } } }};
// 先用可选链确保路径存在,再解构const profile = response?.data?.user?.profile;if (profile) { const { name, contact: { email } = {} } = profile; console.log(name, email); // 'Alice', 'alice@email.com'}注意事项和限制
1. 不能用于赋值
const obj = {};
// ❌ 错误:不能用可选链进行赋值// obj?.prop = 'value'; // SyntaxError
// ✅ 正确:先检查再赋值if (obj) { obj.prop = 'value';}2. 短路求值的性能考虑
const expensiveOperation = () => { console.log('执行了昂贵的计算'); return { result: 42 };};
const obj = null;
// 由于 obj 为 null,expensiveOperation 不会被调用const result = obj?.someMethod?.(expensiveOperation());// 不会打印 '执行了昂贵的计算'3. 与 delete 操作符结合
const user = { profile: { temporaryData: 'should be removed' }};
// ✅ 安全删除delete user?.profile?.temporaryData;
// 对于不存在的路径,delete 也是安全的delete user?.settings?.cache; // 不会报错可选链的优势总结
- 安全性:避免
TypeError异常 - 简洁性:减少冗长的
if检查 - 可读性:代码意图更加清晰
- 维护性:减少因数据结构变化导致的错误
- 性能:短路求值避免不必要的计算
可选链操作符是现代 JavaScript 中处理不确定数据结构的标准做法,特别适用于 API 数据处理、DOM 操作和配置管理等场景。
解构赋值(Destructuring Assignment)
解构赋值是一种方便的语法糖,可以从数组或对象中提取数据,并将这些值分配给变量。它让代码更简洁、易读。
数组解构
数组基本用法
// 传统方式const fruits = ['apple', 'banana', 'orange'];const first = fruits[0];const second = fruits[1];
// 解构方式const [first, second, third] = fruits;console.log(first); // 'apple'console.log(second); // 'banana'console.log(third); // 'orange'跳过元素
const colors = ['red', 'green', 'blue', 'yellow'];
// 只要第一个和第三个const [first, , third] = colors;console.log(first); // 'red'console.log(third); // 'blue'数组默认值
const [a = 1, b = 2, c = 3] = [10, 20];console.log(a); // 10 (实际值)console.log(b); // 20 (实际值)console.log(c); // 3 (默认值)与剩余参数结合
const numbers = [1, 2, 3, 4, 5];const [first, second, ...rest] = numbers;
console.log(first); // 1console.log(second); // 2console.log(rest); // [3, 4, 5]对象解构
对象基本用法
// 传统方式const user = { name: 'Alice', age: 30, city: 'New York' };const name = user.name;const age = user.age;
// 解构方式const { name, age, city } = user;console.log(name); // 'Alice'console.log(age); // 30console.log(city); // 'New York'重命名变量
const user = { name: 'Bob', age: 25 };
// 将 name 重命名为 usernameconst { name: username, age: userAge } = user;console.log(username); // 'Bob'console.log(userAge); // 25对象默认值
const settings = { theme: 'dark' };
const { theme, language = 'en', notifications = true } = settings;console.log(theme); // 'dark'console.log(language); // 'en' (默认值)console.log(notifications); // true (默认值)嵌套解构
const data = { user: { profile: { name: 'Charlie', preferences: { theme: 'light', notifications: true } } }};
// 深层嵌套解构const { user: { profile: { name, preferences: { theme, notifications } } }} = data;
console.log(name); // 'Charlie'console.log(theme); // 'light'console.log(notifications); // true剩余属性 (Rest Properties)
const user = { name: 'David', age: 28, city: 'Boston', country: 'USA' };
const { name, age, ...address } = user;console.log(name); // 'David'console.log(age); // 28console.log(address); // { city: 'Boston', country: 'USA' }函数参数解构
对象参数解构
// 传统方式function createUser(options) { const name = options.name || 'Anonymous'; const age = options.age || 0; const isActive = options.isActive || false; // ...}
// 解构方式function createUser({ name = 'Anonymous', age = 0, isActive = false }) { console.log(`User: ${name}, Age: ${age}, Active: ${isActive}`);}
createUser({ name: 'Eve', age: 22 });// 输出: User: Eve, Age: 22, Active: false数组参数解构
function processCoordinates([x, y, z = 0]) { console.log(`X: ${x}, Y: ${y}, Z: ${z}`);}
processCoordinates([10, 20]); // X: 10, Y: 20, Z: 0processCoordinates([5, 15, 25]); // X: 5, Y: 15, Z: 25实际应用场景
1. 交换变量
let a = 1;let b = 2;
// 传统方式需要临时变量// let temp = a;// a = b;// b = temp;
// 解构方式,一行搞定[a, b] = [b, a];console.log(a, b); // 2, 12. 函数返回多个值
function getMinMax(numbers) { return [Math.min(...numbers), Math.max(...numbers)];}
const [min, max] = getMinMax([3, 1, 4, 1, 5, 9]);console.log(min, max); // 1, 93. 从 API 响应中提取数据
// 模拟 API 响应const apiResponse = { data: { user: { id: 123, profile: { name: 'John', email: 'john@example.com' } } }, status: 'success', message: 'Data retrieved successfully'};
// 直接提取需要的数据const { data: { user: { id: userId, profile: { name: userName, email } } }, status} = apiResponse;
console.log(userId, userName, email, status);// 输出: 123, 'John', 'john@example.com', 'success'4. React 中的 props 解构
// 传统方式function UserCard(props) { return ( <div> <h2>{props.name}</h2> <p>Age: {props.age}</p> <p>Email: {props.email}</p> </div> );}
// 解构方式function UserCard({ name, age, email, isOnline = false }) { return ( <div> <h2>{name} {isOnline ? '🟢' : '🔴'}</h2> <p>Age: {age}</p> <p>Email: {email}</p> </div> );}解构总结
解构赋值的核心优势:
- 简洁性:减少代码量,提高可读性
- 默认值:优雅处理缺失的数据
- 重命名:避免变量名冲突
- 深层提取:直接访问嵌套数据
- 函数参数:让函数接口更清晰
解构赋值是现代 JavaScript 开发中不可或缺的语法糖,特别是在处理复杂数据结构和函数参数时。