0

0

什么是可选链和空值合并运算符,以及它们如何简化深层对象访问和默认值处理?

夜晨

夜晨

发布时间:2025-09-21 18:17:01

|

797人浏览过

|

来源于php中文网

原创

可选链(?.)和空值合并运算符(??)提升了JavaScript中处理null/undefined的安全性与简洁性:可选链避免深层属性访问时的TypeError,空值合并精准设置默认值,二者结合实现安全、清晰、健壮的数据访问模式。

什么是可选链和空值合并运算符,以及它们如何简化深层对象访问和默认值处理?

可选链 (

?.
) 和空值合并运算符 (
??
) 是 JavaScript 中两个非常实用的新特性,它们的核心作用就是以更简洁、更安全的方式处理可能为
null
undefined
的值。说白了,它们极大地简化了我们访问深层对象属性时,避免
TypeError
的繁琐检查,同时也能更精准地为变量设置默认值,让代码看起来更清爽,也更健壮。

解决方案

在 JavaScript 的世界里,当我们尝试访问一个不存在的属性,或者一个

null
/
undefined
值的属性时,通常会遭遇
TypeError
的尴尬。这在处理来自后端接口、用户输入或者复杂配置对象时尤为常见。可选链和空值合并运算符正是为解决这些痛点而生。

可选链运算符 (

?.
)

这个运算符的出现,简直是前端开发者的福音。它允许我们安全地访问对象或数组深层嵌套的属性,而无需进行层层

null
undefined
的检查。如果链中的某个引用是
null
undefined
,表达式会立即停止求值并返回
undefined
,而不是抛出错误。

想象一下以前我们怎么写:

const user = {
  profile: {
    address: {
      street: 'Main St'
    }
  }
};

// 以前的写法,为了安全访问深层属性
let streetName = '';
if (user && user.profile && user.profile.address && user.profile.address.street) {
  streetName = user.profile.address.street;
}
console.log(streetName); // Main St

const anotherUser = {};
let anotherStreetName = '';
if (anotherUser && anotherUser.profile && anotherUser.profile.address && anotherUser.profile.address.street) {
  anotherStreetName = anotherUser.profile.address.street;
}
console.log(anotherStreetName); // '' (不会报错,但代码冗长)

现在有了可选链,代码可以这样写:

const user = {
  profile: {
    address: {
      street: 'Main St'
    }
  }
};

const streetName = user?.profile?.address?.street;
console.log(streetName); // Main St

const anotherUser = {};
const anotherStreetName = anotherUser?.profile?.address?.street;
console.log(anotherStreetName); // undefined (不会报错,简洁明了)

// 甚至可以用于函数调用或数组访问
const config = {
  getSetting: () => 'some value'
};
const setting = config?.getSetting?.(); // 安全调用方法
console.log(setting); // some value

const dataArray = [{ id: 1 }];
const firstId = dataArray?.[0]?.id; // 安全访问数组元素
console.log(firstId); // 1

const emptyArray = [];
const nonExistentId = emptyArray?.[0]?.id;
console.log(nonExistentId); // undefined

空值合并运算符 (

??
)

这个运算符主要用于为变量提供一个默认值,但它比逻辑或运算符

||
更精确。
??
只有当左侧的操作数为
null
undefined
时,才会返回右侧的操作数。而
||
运算符会在左侧操作数为任何“假值”(
false
,
0
,
''
,
null
,
undefined
,
NaN
)时,都返回右侧操作数。

这个区别在处理数字

0
或空字符串
''
这类有效值时显得尤为重要。

// 以前使用 || 设置默认值的问题
const count = 0;
const displayCount_old = count || 10; // 0 是假值,所以会得到 10
console.log(displayCount_old); // 10 (这里可能不是我们想要的,如果 0 是有效值)

const message = '';
const displayMessage_old = message || 'Default Message'; // '' 是假值,会得到 'Default Message'
console.log(displayMessage_old); // Default Message (同样,如果空字符串是有效值,这就不对了)

// 使用 ?? 解决这个问题
const count_new = 0;
const displayCount_new = count_new ?? 10; // 0 不是 null 或 undefined,所以得到 0
console.log(displayCount_new); // 0 (这通常是期望的行为)

const message_new = '';
const displayMessage_new = message_new ?? 'Default Message'; // '' 不是 null 或 undefined,所以得到 ''
console.log(displayMessage_new); // '' (同样,符合预期)

const username = null;
const displayUsername = username ?? 'Guest';
console.log(displayUsername); // Guest

const age = undefined;
const displayAge = age ?? 18;
console.log(displayAge); // 18

为什么在处理复杂数据结构时,可选链能显著提升代码的健壮性与可读性?

在我看来,可选链的出现,是现代 JavaScript 语法对开发者心智负担的一次极大解放。想想看,在没有它之前,每当我们从一个接口获取数据,比如一个用户对象,里面嵌套着地址、联系方式、权限列表等等,我们为了确保程序不崩溃,总得写一堆

if (user && user.address && user.address.city)
这样的防御性代码。这不仅让代码变得异常冗长,而且一眼望去,全是条件判断,真正的业务逻辑反而被淹没了。

可选链的魔力在于,它把这些繁琐的

null
/
undefined
检查,以一种声明式、内联的方式解决了。你直接写出你想要访问的路径,如果中间哪一环断了,它就默默地返回
undefined
,而不是给你一个刺眼的
TypeError
。这就像是给你的数据访问路径装了一个智能的“保险丝”,一旦遇到空值,它就自动断开,保护了整个程序的稳定运行。

从健壮性角度看,它直接杜绝了因深层属性不存在而导致的运行时错误,大大降低了生产环境中的潜在崩溃风险。再者,从可读性上讲,代码变得异常简洁,意图也更加清晰。你一眼就能看出开发者想要访问的是哪个属性,而不是被一堆无关紧要的

if
语句分散注意力。这种“所见即所得”的编程体验,对于维护复杂项目来说,简直是效率的飞跃。我经常在重构老代码时,把那些层层嵌套的
if
替换成可选链,代码瞬间清爽,心情也跟着好了起来。

空值合并运算符与逻辑或运算符
||
有何本质区别,以及何时应该优先选择
??

这俩兄弟乍一看都像是在给变量设置默认值,但骨子里却有着根本的不同,而这个不同,正是

??
存在的意义。

陌言AI
陌言AI

陌言AI是一个一站式AI创作平台,支持在线AI写作,AI对话,AI绘画等功能

下载

逻辑或运算符

||
的判断逻辑是“左侧操作数为假值(falsy)时,返回右侧操作数”。假值在 JavaScript 里可不少,除了
null
undefined
,还包括
0
、空字符串
''
false
NaN
。这意味着,如果你想给一个变量设置默认值,但这个变量本身可能合法地是
0
''
,那么
||
就会“误判”,把它当成需要替换的假值。

举个例子,假设一个商品的库存数量可能是

0
,而你用
||
来设置默认库存:

const stock = 0;
const displayStock = stock || 100; // 结果是 100,因为 0 是假值

这里,

0
显然是一个有效的库存数量,但
||
却把它替换成了
100
,这显然与我们的业务逻辑不符。

而空值合并运算符

??
则要“严谨”得多。它只关心左侧操作数是否为
null
undefined
。只有在这两种情况下,它才会返回右侧的操作数。对于
0
''
false
甚至
NaN
??
都会把它们视为有效值,直接返回。

因此,当你需要为变量提供默认值,并且希望

0
、空字符串
''
false
等值被视为有效值时,
??
才是你的首选。它提供了一种更精准、更符合直觉的默认值处理机制,避免了
||
在某些场景下的“过度”判断。在我看来,只要是涉及到默认值赋值,并且你预期
0
''
可能是有效输入时,无脑用
??
准没错,它能帮你避免很多不必要的逻辑错误。

在实际开发中,如何结合可选链与空值合并运算符,构建更安全、更简洁的数据访问模式?

在日常开发中,可选链和空值合并运算符常常是“搭档”出现,它们一起构建了一种非常优雅、安全且高效的数据访问模式。这种组合在处理从后端获取的、结构可能不完全确定的数据时尤其强大。

设想一个场景:我们正在开发一个用户详情页面,需要展示用户的联系方式。这个联系方式可能深藏在

user.profile.contact.phone
路径下,而且
profile
contact
甚至
phone
本身都可能不存在或为
null
/
undefined
。如果
phone
最终是
null
undefined
,我们希望显示一个默认的提示信息,比如“暂无电话”。

传统的写法可能长这样:

const userData = {
  id: 1,
  name: '张三',
  profile: {
    address: '北京',
    contact: {
      email: 'zhangsan@example.com'
      // phone 字段可能不存在
    }
  }
};

let userPhone = '暂无电话';
if (userData && userData.profile && userData.profile.contact && userData.profile.contact.phone) {
  userPhone = userData.profile.contact.phone;
}
console.log(userPhone); // 暂无电话

// 另一个例子,phone存在但为null
const userData2 = {
  id: 2,
  name: '李四',
  profile: {
    address: '上海',
    contact: {
      email: 'lisi@example.com',
      phone: null
    }
  }
};

let userPhone2 = '暂无电话';
if (userData2 && userData2.profile && userData2.profile.contact && userData2.profile.contact.phone) {
  userPhone2 = userData2.profile.contact.phone;
}
console.log(userPhone2); // 暂无电话 (同样冗长)

而结合可选链和空值合并运算符,我们可以这样写:

const userData = {
  id: 1,
  name: '张三',
  profile: {
    address: '北京',
    contact: {
      email: 'zhangsan@example.com'
      // phone 字段可能不存在
    }
  }
};

// 简洁且安全地获取电话,如果不存在或为 null/undefined,则使用默认值
const userPhone = userData?.profile?.contact?.phone ?? '暂无电话';
console.log(userPhone); // 暂无电话

// 另一个例子,phone存在但为null
const userData2 = {
  id: 2,
  name: '李四',
  profile: {
    address: '上海',
    contact: {
      email: 'lisi@example.com',
      phone: null
    }
  }
};

const userPhone2 = userData2?.profile?.contact?.phone ?? '暂无电话';
console.log(userPhone2); // 暂无电话

// 假设电话号码是 0 (比如某种内部编码)
const userData3 = {
  id: 3,
  name: '王五',
  profile: {
    address: '广州',
    contact: {
      email: 'wangwu@example.com',
      phone: 0
    }
  }
};

const userPhone3 = userData3?.profile?.contact?.phone ?? '暂无电话';
console.log(userPhone3); // 0 (正确地保留了 0 这个有效值)

这种模式的强大之处在于:

  1. 安全访问
    ?.
    确保了即使
    profile
    contact
    对象不存在,整个表达式也不会抛出
    TypeError
    ,而是安全地返回
    undefined
  2. 精准默认值:紧接着的
    ??
    运算符,会检查
    ?.
    返回的结果。如果这个结果是
    undefined
    (因为某个中间属性缺失)或者
    null
    (因为最终属性值为
    null
    ),它就会提供我们预设的默认值
    '暂无电话'
    。而如果最终属性值是
    0
    或空字符串
    ''
    ,它会正确地保留这些有效值,而不是错误地替换掉。

通过这种组合,我们不仅大大减少了代码量,提升了可读性,更重要的是,它构建了一个异常鲁棒的数据访问路径,让我们的应用在面对各种不确定数据时,依然能够稳定运行,并提供友好的用户体验。这在处理大型、复杂的数据模型时,简直是提升开发效率和代码质量的利器。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

557

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

394

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

754

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

478

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

454

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1051

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

658

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

554

2023.09.20

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

1

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
手把手实现数据传输编码
手把手实现数据传输编码

共1课时 | 728人学习

PHP面向对象基础课程(更新中)
PHP面向对象基础课程(更新中)

共12课时 | 0.7万人学习

光速学会docker容器
光速学会docker容器

共33课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号