前言
之前看到了一篇很棒的帖子,觉得不做就做了下笔记特与大家分享!通常情况下我都会在总结的同时,在文末附带【阅读原文】,这次是真找不到原文了(时间有点久远了),但是我还是有尊重原创的意识的[狗头]··· 希望本篇文章能够给大家带来一些收获~?
可选链操作符
可选链操作符( ?. )
允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.
操作符的功能类似于 .
链式操作符,不同之处在于,在引用为空(nullish
) (null
或者 undefined
) 的情况下不会引起错误,该表达式短路返回值是undefined
。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined
。
当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链操作符也是很有帮助的。
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah'
}
}
const dogName = adventurer.dog?.name
// expected output: undefined
console.log(dogName) // undefined
// expected output: undefined
console.log(adventurer.someNonExistentMethod?.()) // undefined
复制代码
语法
obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)
复制代码
描述
通过连接的对象的引用或函数可能是undefined
或null
时,可选链操作符提供了一种方法来简化被连接对象的值访问。
比如,思考一个存在嵌套结构的对象obj
。不使用可选链
的话,查找一个深度嵌套的子属性时,需要验证之间的引用,例如:
let nestedProp = obj.first && obj.first.second
复制代码
为了避免报错,在访问obj.first.second
之前,要保证obj.first
的值既不是null
,也不是 undefined
。如果只是直接访问 obj.first.second
,而不对 obj.first
进行校验,则有可能抛出错误。
有了可选链操作符(?.)
,在访问 obj.first.second
之前,不再需要明确地校验 obj.first
的状态,再并用短路计算获取最终结果:
let nestedProp = obj.first?.second
复制代码
通过使用 ?.
操作符取代 .
操作符,JavaScript 会在尝试访问obj.first.second
之前,先隐式地检查并确定 obj.first
既不是 null
也不是 undefined
。如果obj.first
是null
或者undefined
,表达式将会短路计算直接返回 undefined
。
这等价于以下表达式,但实际上没有创建临时变量:
let temp = obj.first
let nestedProp = temp === null || temp === undefined ? undefined : temp.second
复制代码
可选链与函数调用
当尝试调用一个可能不存在的方法时也可以使用可选链。这将是很有帮助的,比如,当使用一个 API 的方法可能不可用时,要么因为实现的版本问题要么因为当前用户的设备不支持该功能。
函数调用时如果被调用的方法不存在,使用可选链可以使表达式自动返回undefined
而不是抛出一个异常。
let result = someInterface.customMethod?.()
复制代码
注意: 如果存在一个属性名且不是函数, 使用 ?. 仍然会产生一个 TypeError 异常 (x.y is not a function).
处理可选的回调函数或者事件处理器
如果使用解构赋值来解构的一个对象的回调函数或 fetch 方法,你可能得到不能当做函数直接调用的不存在的值,除非你已经校验了他们的存在性。使用?.的你可以忽略这些额外的校验:
// ES2019的写法
function doSomething(onContent, onError) {
try {
// ... do something with the data
} catch (err) {
if (onError) {
// 校验onError是否真的存在
onError(err.message)
}
}
}
复制代码
// ES2020的写法 使用可选链进行函数调用
function doSomething(onContent, onError) {
try {
// ... do something with the data
} catch (err) {
onError?.(err.message) // 如果onError是undefined也不会有异常
}
}
复制代码
可选链和表达式
当使用方括号与属性名的形式来访问属性时,你也可以使用可选链操作符:
let nestedProp = obj?.['prop' + 'Name']
复制代码
可选链不能用于赋值
let object = {}
object?.property = 1 // Uncaught SyntaxError: Invalid left-hand side in assignment
复制代码
可选链访问数组元素
let arrayItem = arr?.[42]
复制代码
例子
基本例子
如下的例子在一个不含 bar
成员的 Map 中查找bar
成员的name
属性,因此结果是 undefined
。
let myMap = new Map()
myMap.set('foo', { name: 'baz', desc: 'inga' })
let nameBar = myMap.get('bar')?.name
复制代码
短路计算
当在表达式中使用可选链时,如果左操作数是null
或undefined
,表达式将不会被计算,例如:
let potentiallyNullObj = null
let x = 0
let prop = potentiallyNullObj?.[x++]
console.log(x) // x 将不会被递增,依旧输出 0
复制代码
连用可选链操作符
可以连续使用可选链读取多层嵌套结构:
let customer = {
name: 'Carl',
details: {
age: 82,
location: 'Paradise Falls' // details 的 address 属性未有定义
}
}
let customerCity = customer.details?.address?.city
// … 可选链也可以和函数调用一起使用
let duration = vacations.trip?.getTime?.()
复制代码
使用空值合并操作符
空值合并操作符可以在使用可选链时设置一个默认值:
let customer = {
name: 'Carl',
details: { age: 82 }
}
let customerCity = customer?.city ?? '暗之城'
console.log(customerCity) // “暗之城”
复制代码
空值合并操作符
空值合并操作符(??)
是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
与逻辑或操作符(||
)不同,逻辑或操作符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 ||
来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,''
或 0
)时。见下面的例子。
const foo = null ?? 'default string'
// expected output: "default string"
console.log(foo) // "default string"
const baz = 0 ?? 42
// expected output: 0
console.log(baz) // 0
复制代码
语法
leftExpr ?? rightExpr
复制代码
使用空值合并操作符
在这个例子中,我们使用空值合并操作符为常量提供默认值,保证常量不为 null
或者 undefined
。
const nullValue = null
const emptyText = '' // 空字符串,是一个假值,Boolean("") === false
const someNumber = 42
const valA = nullValue ?? 'valA 的默认值'
const valB = emptyText ?? 'valB 的默认值'
const valC = someNumber ?? 0
console.log(valA) // "valA 的默认值"
console.log(valB) // ""(空字符串虽然是假值,但不是 null 或者 undefined)
console.log(valC) // 42
复制代码
为变量赋默认值
以前,如果想为一个变量赋默认值,通常的做法是使用逻辑或操作符(||):
let foo
// foo is never assigned any value so it is still undefined
let someDummyText = foo || 'Hello!'
复制代码
然而,由于 ||
是一个布尔逻辑运算符,左侧的操作数会被强制转换成布尔值用于求值。任何假值(0
, ''
, NaN
, null
, undefined
)都不会被返回。这导致如果你使用0
,''
或NaN
作为有效值,就会出现不可预料的后果。
let count = 0 //
let text = ''
let qty = count || 42
let message = text || 'hi!'
console.log(qty) // 42,而不是 0
console.log(message) // "hi!",而不是 ""
复制代码
空值合并操作符可以避免这种陷阱,其只在第一个操作数为null
或 undefined
时(而不是其它假值)返回第二个操作数:
let myText = '' // An empty string (which is also a falsy value)
let notFalsyText = myText || 'Hello world'
console.log(notFalsyText) // Hello world
let preservingFalsy = myText ?? 'Hi neighborhood'
console.log(preservingFalsy) // '' (as myText is neither undefined nor null)
复制代码
短路
与 OR 和 AND 逻辑操作符相似,当左表达式不为 null
或 undefined
时,不会对右表达式进行求值。
function A() {
console.log('函数 A 被调用了')
return undefined
}
function B() {
console.log('函数 B 被调用了')
return false
}
function C() {
console.log('函数 C 被调用了')
return 'foo'
}
console.log(A() ?? C())
// 依次打印 "函数 A 被调用了"、"函数 C 被调用了"、"foo"
// A() 返回了 undefined,所以操作符两边的表达式都被执行了
console.log(B() ?? C())
// 依次打印 "函数 B 被调用了"、"false"
// B() 返回了 false(既不是 null 也不是 undefined)
// 所以右侧表达式没有被执行
复制代码
不能与 AND 或 OR 操作符共用
将 ??
直接与 AND(&&
)和 OR(||
)操作符组合使用是不可取的。(译者注:应当是因为空值合并操作符和其他逻辑操作符之间的运算优先级/运算顺序是未定义的)这种情况下会抛出 SyntaxError 。
// bad
null || undefined ?? "foo"; // 抛出 SyntaxError
true || undefined ?? "foo"; // 抛出 SyntaxError
复制代码
但是,如果使用括号来显式表明运算优先级,是没有问题的:
// good
;(null || undefined) ?? 'foo' // 返回 "foo"
复制代码
与可选链式操作符(?.)的关系
空值合并操作符针对 undefined
与 null
这两个值,可选链式操作符(?.) 也是如此。在这访问属性可能为 undefined
与 null
的对象时,可选链式操作符非常有用。
let foo = { someFooProp: 'hi' }
console.log(foo.someFooProp?.toUpperCase()) // "HI"
console.log(foo.someBarProp?.toUpperCase()) // undefined
复制代码