??(空值合并操作符)与?.(可选链操作符)
2022/1/19大约 5 分钟
ES2020 引入了两个非常实用的新操作符:空值合并操作符(??
)和可选链操作符(?.
)。 这两个操作符简化了处理可能为 null
或 undefined
值的方式。
空值合并操作符(??)
基本概念
空值合并操作符 ??
是一个逻辑操作符,当左侧的值为 null
或 undefined
时,返回右侧的值;否则返回左侧的值。
语法
leftExpression ?? rightExpression
与逻辑或操作符(||)的区别
在 ??
出现之前,我们通常使用逻辑或操作符 ||
来提供默认值。但两者有一个重要的区别:
||
会在左侧值为假值(如0
、''
、false
、null
、undefined
、NaN
)时返回右侧值??
只会在左侧值为空值(如null
或undefined
)时返回右侧值
这意味着,如果我们希望在左侧值为 0
、''
或 false
时仍然返回左侧值,那么 ??
是更好的选择。
示例
// 传统方式使用 ||
const count1 = userInput || 10;
// 如果 userInput 为 0,会返回 10,这可能不是我们想要的
// 使用 ??
const count2 = userInput ?? 10;
// 只有当 userInput 为 null 或 undefined 时,才会返回 10
// 如果 userInput 为 0、'' 或 false,仍然返回 userInput 的值
// 实际应用示例
function processUserInput(input) {
const username = input.username ?? 'Guest';
const age = input.age ?? 18;
const isVIP = input.isVIP ?? false;
console.log(`用户名: ${username}, 年龄: ${age}, VIP: ${isVIP}`);
}
// 测试
processUserInput({ username: '张三', age: 25 });
// 输出: 用户名: 张三, 年龄: 25, VIP: false
processUserInput({ username: '', age: 0, isVIP: true });
// 输出: 用户名: , 年龄: 0, VIP: true
processUserInput({});
// 输出: 用户名: Guest, 年龄: 18, VIP: false
可选链操作符(?.)
基本概念
可选链操作符 ?.
允许我们读取对象链深处的属性值,而不必检查链中的每个引用是否有效。如果链中的某个引用为 null
或 undefined
,表达式会短路并返回 undefined
,而不是抛出错误。
语法
obj?.prop
obj?.[expr]
func?.(args)
与传统条件检查的对比
在 ?.
出现之前,我们需要使用嵌套的条件语句或逻辑与操作符 &&
来安全地访问嵌套属性,这会使代码变得冗长且难以阅读。
代码示例
// 传统方式
const username = user && user.profile && user.profile.name;
// 使用可选链
const username = user?.profile?.name;
// 实际应用示例
function getUserInfo(user) {
// 安全地访问嵌套属性
const username = user?.profile?.name ?? 'Unknown';
const avatar = user?.profile?.avatar ?? '/default-avatar.png';
const city = user?.address?.city ?? 'Unknown City';
const phone = user?.contacts?.phone ?? 'No phone number';
// 安全地调用方法
const greeting = user?.greet?.() ?? 'Hello!';
// 安全地访问数组元素
const firstFriend = user?.friends?.[0]?.name ?? 'No friends yet';
return {
username,
avatar,
city,
phone,
greeting,
firstFriend
};
}
// 测试
const user1 = {
profile: {
name: '李四',
avatar: '/avatar1.png'
},
address: {
city: '北京'
},
contacts: {
phone: '13800138000'
},
greet() {
return `Hi, I'm ${this.profile.name}!`;
},
friends: [{ name: '王五' }]
};
console.log(getUserInfo(user1));
/* 输出:
{
username: '李四',
avatar: '/avatar1.png',
city: '北京',
phone: '13800138000',
greeting: "Hi, I'm 李四!",
firstFriend: '王五'
}
*/
// 测试不完整的用户对象
console.log(getUserInfo({}));
/* 输出:
{
username: 'Unknown',
avatar: '/default-avatar.png',
city: 'Unknown City',
phone: 'No phone number',
greeting: 'Hello!',
firstFriend: 'No friends yet'
}
*/
// 测试 null
console.log(getUserInfo(null));
/* 输出:
{
username: 'Unknown',
avatar: '/default-avatar.png',
city: 'Unknown City',
phone: 'No phone number',
greeting: 'Hello!',
firstFriend: 'No friends yet'
}
*/
组合使用 ?? 和 ?.
??
和 ?.
可以很好地结合使用,让我们能够简洁地处理可能不存在的属性并提供默认值。
// 组合使用示例
const user = {
profile: {
name: '赵六'
// 没有 age 属性
}
// 没有 address 属性
};
// 获取用户年龄,如果不存在则默认为 18
const age = user?.profile?.age ?? 18;
console.log(age); // 输出: 18
// 获取用户城市,如果不存在则默认为 'Unknown'
const city = user?.address?.city ?? 'Unknown';
console.log(city); // 输出: 'Unknown'
// 在函数参数中使用
function displayUserInfo(user) {
const name = user?.name ?? 'Guest';
const role = user?.role ?? 'User';
const accessLevel = user?.settings?.accessLevel ?? 1;
console.log(`${name} (${role}) - Access Level: ${accessLevel}`);
}
// 调用函数
const admin = { name: 'Admin', role: 'Administrator', settings: { accessLevel: 5 } };
const regularUser = { name: 'User1' };
const guest = null;
displayUserInfo(admin); // 输出: Admin (Administrator) - Access Level: 5
displayUserInfo(regularUser); // 输出: User1 (User) - Access Level: 1
displayUserInfo(guest); // 输出: Guest (User) - Access Level: 1
应用场景
1. 处理 API 响应数据
在处理来自 API 的响应数据时,我们经常需要处理可能缺失的字段。使用 ??
和 ?.
可以使我们的代码更加健壮。
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// 安全地获取用户数据并提供默认值
return {
id: data?.id ?? userId,
name: data?.name ?? 'Unknown',
email: data?.email ?? 'No email provided',
posts: data?.posts ?? [],
lastActive: data?.lastActive ?? new Date().toISOString()
};
} catch (error) {
console.error('Error fetching user data:', error);
return {
id: userId,
name: 'Unknown',
email: 'No email provided',
posts: [],
lastActive: new Date().toISOString()
};
}
}
2. 配置对象处理
在处理配置对象时,我们可以使用 ??
和 ?.
来安全地获取配置值并提供合理的默认值。
function setupApplication(config = {}) {
// 获取配置值或使用默认值
const apiEndpoint = config?.api?.endpoint ?? 'https://api.example.com';
const timeout = config?.api?.timeout ?? 5000;
const theme = config?.ui?.theme ?? 'light';
const notifications = config?.notifications?.enabled ?? true;
const maxRetries = config?.api?.maxRetries ?? 3;
// 应用配置
console.log(`Setting up app with:`);
console.log(`- API Endpoint: ${apiEndpoint}`);
console.log(`- Timeout: ${timeout}ms`);
console.log(`- Theme: ${theme}`);
console.log(`- Notifications: ${notifications ? 'enabled' : 'disabled'}`);
console.log(`- Max Retries: ${maxRetries}`);
// 返回实际使用的配置
return {
api: {
endpoint: apiEndpoint,
timeout,
maxRetries
},
ui: {
theme
},
notifications: {
enabled: notifications
}
};
}
// 测试
const customConfig = {
api: {
endpoint: 'https://custom-api.example.com',
maxRetries: 5
},
ui: {
theme: 'dark'
}
};
const finalConfig = setupApplication(customConfig);
/* 输出:
Setting up app with:
- API Endpoint: https://custom-api.example.com
- Timeout: 5000ms
- Theme: dark
- Notifications: enabled
- Max Retries: 5
*/
浏览器兼容性
相关信息
- 表格中的数字表示该浏览器开始支持的最低版本
- "No" 表示该浏览器不支持此功能
PC 端
浏览器功能 | Chrome | Edge | Firefox | Opera | Safari |
---|---|---|---|---|---|
空值合并操作符 (?? ) | 80 | 80 | 74 | 67 | 13.1 |
可选链操作符 (?. ) | 80 | 80 | 74 | 67 | 13.1 |
移动端
浏览器功能 | Chrome Android | Firefox Android | Opera Android | Safari iOS | WebView Android |
---|---|---|---|---|---|
空值合并操作符 (?? ) | 80 | 74 | 57 | 13.4 | 80 |
可选链操作符 (?. ) | 80 | 74 | 57 | 13.4 | 80 |
注意事项
- IE 浏览器兼容性:Internet Explorer 浏览器完全不支持这两个操作符
- 转译支持:如果需要在不支持这些操作符的环境中使用,可以通过 Babel 等转译工具进行转换
- Node.js 支持:Node.js 从版本 14 开始支持这两个操作符
- TypeScript 支持:TypeScript 从版本 3.7 开始支持这两个操作符的类型检查
更新日志
2025/9/28 09:03
查看所有更新日志
38e56
-于