聊聊新特性操作符 ! / ? / ??
背景
最近接触了 ts + react 的项目,项目代码中看到有这样的写法
function demo() {
return obj.a!; // what's the meaning of 「!」
}
const demoTwo = obj?.a.aChild; // what's the meaning of 「?」
const demoThird = obj ?? anotherObj; // what's the meaning of 「??」
相信前端的同学都能明白 「?」 「??」的意思。如果没接触过 ts,那么对 「!」 大概、也许是陌生的(没错,我说的前端同学就是我自己)
本来以为 [ ? | ?? ]是 es6 的语法,想多了,是 es11(es 2020)了都 😂
看到了,所以想自己记录下,省的每次记不起来又再重复搜索,搜了忘,忘了搜的。好记性不如烂笔头么,记录一下我的学习瞬间。🐶
「?」
名字:可选链/ optionalChaining
可选链操作符(
?.
)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.
操作符的功能类似于.
链式操作符,不同之处在于,在引用为空(nullish ) (null
或者undefined
) 的情况下不会引起错误,该表达式短路返回值是undefined
。与函数调用一起使用时,如果给定的函数不存在,则返回undefined
。—— from MDN 可选链
Q:这个操作符解决什么场景的问题呢?
A:有的时候需要对一个对象里边很深的属性取值,但是中间取值的过程可能有的属性为 undefined/null,如果不做处理,就会直接报错了,js 这单线程的玩意儿,一报错后面直接 gg。有了这个操作符,取值的时候不再报错,取不到值会直接返回 undefined。
const demo = obj.a.child.children;
// 以前如果取这样的值,且为了防止报错,会这样写
const demo = obj && obj.a && obj.a.child && obj.a.child.children;
// 丑、可读性差也就不说了,打这么多字,腱鞘炎又要严重了有木有
// 为了解决这个问题,引入了 ? 操作符
// 新写法
const demo = obj?.a?.child?.children;
// 如果中间某个属性值为 undefined/null,也不会报错。demo 直接为 undefined
「??」
名称:空值合并运算符(Nullish coalescing Operator)
空值合并操作符(
??
)是一个逻辑操作符,当左侧的操作数为null
或者undefined
时,返回其右侧操作数,否则返回左侧操作数。—— from MDN 空值合并运算符
Q:这个操作符解决什么场景的问题呢?不是有 || 了么?
A:逻辑或操作符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 ||
来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,''
或 0
)时。(来自MDN)
// 这个例子很好说明了,不再赘述
let user = {
u1: 0,
u2: false,
u3: null,
u4: undefined
u5: '',
}
let u1 = user.u1 ?? '用户1' // 0
let u2 = user.u2 ?? '用户2' // false
let u3 = user.u3 ?? '用户3' // 用户3
let u4 = user.u4 ?? '用户4' // 用户4
let u5 = user.u5 ?? '用户5' // ''
「!」
名称:非空断言操作符(Non-Null Assertion Operator)
A new
!
post-fix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operationx!
produces a value of the type ofx
withnull
andundefined
excluded. Similar to type assertions of the forms<T>x
andx as T
, the!
non-null assertion operator is simply removed in the emitted JavaScript code.——from TS 官网 非空断言
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符
!
可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。! 等同于<T>x
andx as T
—— from 细数 TS 中那些奇怪的符号
特别指出,ts 语言编译成 js 后,非空断言操作符会被移除。
🌟 所以如果要用这个操作符,然后编码还会编译成js的话,注意异常的情况。
我不想写了,引用了 细数 TS 中那些奇怪的符号 的内容
忽略 undefined 和 null 类型
function myFunc(maybeString: string | undefined | null) { // Type 'string | null | undefined' is not assignable to type 'string'. // Type 'undefined' is not assignable to type 'string'. const onlyString: string = maybeString; // Error const ignoreUndefinedAndNull: string = maybeString!; // Ok }
调用函数时忽略 undefined 类型
type NumGenerator = () => number; function myFunc(numGenerator: NumGenerator | undefined) { // Object is possibly 'undefined'.(2532) // Cannot invoke an object which is possibly 'undefined'.(2722) const num1 = numGenerator(); // Error const num2 = numGenerator!(); //OK }
因为
!
非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要特别注意。比如下面这个例子:const a: number | undefined = undefined; const b: number = a!; console.log(b);
以上 TS 代码会编译生成以下 ES5 代码:
"use strict"; const a = undefined; const b = a; console.log(b);
虽然在 TS 代码中,我们使用了非空断言,使得
const b: number = a!;
语句可以通过 TypeScript 类型检查器的检查。但在生成的 ES5 代码中,!
非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出undefined
。
附录[3] 中有提到 「可选链」和「非空断言」的区别,我觉得例子写的挺直观的,也贴在这儿了。
const arr = [{value: 1}, {value: 2}];
const item = arr.find(el => el.value === 2);
const result = item.value + 1; //TS error, since item is (allegedly) possibly undefined
如果用可选链 item?.value
时,也挡不住 表达式 undefined +1 的错误。在这个场景下使用 非空断言,排除 null/undefined,可以避免一部分错误。
附录[4]
Non-null assertion operator will not *null guard* your property chain. It just tells TypeScript that the value is never going to be null. But, if your value is indeed null, then your code will crash.
总结
! 是给类型检查用的,于代码逻辑中不起任何作用
? 是真的会影响代码逻辑的
附录
[1] 细数 TS 中那些奇怪的符号
[2] TypeScript 中的代码清道夫:非空断言操作符-中文版