你真的很懂·操作符???

878 阅读7分钟

前言

之前看到了一篇很棒的帖子,觉得不做就做了下笔记特与大家分享!通常情况下我都会在总结的同时,在文末附带【阅读原文】,这次是真找不到原文了(时间有点久远了),但是我还是有尊重原创的意识的[狗头]··· 希望本篇文章能够给大家带来一些收获~🌚

关键词: 可选链操作符 , 空值合并操作符, ES2020

可选链操作符

可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 .链式操作符,不同之处在于,在引用为空(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)

描述

通过连接的对象的引用或函数可能是undefinednull时,可选链操作符提供了一种方法来简化被连接对象的值访问。

比如,思考一个存在嵌套结构的对象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.firstnull或者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

短路计算

当在表达式中使用可选链时,如果左操作数是nullundefined,表达式将不会被计算,例如:

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''NaNnullundefined)都不会被返回。这导致如果你使用0''NaN作为有效值,就会出现不可预料的后果。

let count = 0 //
let text = ''

let qty = count || 42
let message = text || 'hi!'

console.log(qty) // 42,而不是 0
console.log(message) // "hi!",而不是 ""

空值合并操作符可以避免这种陷阱,其只在第一个操作数为nullundefined 时(而不是其它假值)返回第二个操作数:

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 逻辑操作符相似,当左表达式不为 nullundefined 时,不会对右表达式进行求值。

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"

与可选链式操作符(?.)的关系

空值合并操作符针对 undefinednull 这两个值,可选链式操作符(?.) 也是如此。在这访问属性可能为 undefinednull 的对象时,可选链式操作符非常有用。

let foo = { someFooProp: 'hi' }

console.log(foo.someFooProp?.toUpperCase()) // "HI"
console.log(foo.someBarProp?.toUpperCase()) // undefined

表达式和运算符分类·MDN