壹 ❀ 引

说来也比较惭愧,可选链操作符?.在公司项目中使用特别频繁,而我在之前一直以为是类似奇技淫巧的写法,所以也没去查阅相关文档,直到在学习typescript时碰巧看到了可选链操作符与空值合并操作符两个概念,才知道原来是ECMAScript官方提出的新语法。因为本人觉得确实非常好用,所以这里做个简单科普,本文开始。

贰 ❀ 可选链操作符?.

贰 ❀ 壹 基本用法

在接近一年的各类bug修复工作中,我发现安全取值问题是bug产生的高频原因之一,或许大家对于数据来源过于自信(或者粗心大意),认为这份数据一定符合代码预期,它一定是个数组或者它一个是个对象,然而在某种未知情况下,你用于遍历的数组报错了,用于获取属性的对象也报错了,恭喜你,不幸解锁成就Uncaught TypeError: Cannot read property 'XX' of null

因此在一次公司前端月会上,我提了一个小意见,凡是数据来源带有查找性质,建议做安全取值判断,比如:

// arr来源是通过其它查找行为所生成,可能是数组也可能是null
arr && arr.forEach((e)=>console.log(e)) // obj来源也是查找,可能是null
let name = obj && obj.name // fn通过props传递,可能没传
fn && fn(...rest);

上述代码应该特别好理解,对于a && b来说,只有a为真时逻辑才会走到b,上述逻辑等同于:

if(obj){
let name = obj.name;
};

而可选链操作符?.的出现会进一步简化我们的写法,而且可读性更加友好,比如:

arr?.forEach((e)=>console.log(e));

let name = obj?.name;

fn?.(...rest);

事实上,我们在开发中常常遇到属性多层级的获取场景,比如下面这样的一个对象,我们需要获取name字段:

let o = {
user:{
name:'echo'
}
};
// 保险做法
let name = o.user && o.user.name; // 换成?.
let name = o.user?.name;

你看,原本臃肿的判断逻辑我们现在直接简化成了一小段,?的含义也很好理解,o.user是真值吗?是那就通过.获取name字段。

贰 ❀ 更强的代码健壮性

?.除了简化代码量提升语义化外,其实更重要的一点是提升了代码的健壮性,一定程度上减少了安全取值类的低级错误,比如一个对象的值意外的是null或者undefined,我们在获取name时:

let o = null;//或者undefined都行
// 犯错的写法
let name = o.name;// 报错,告诉你不能从null上读取name属性
// 换成?.
let name = o?.name;
console.log(name);//undefined

当换成?.这段代码并不会报错,而且当我们访问name时就像单纯声明了一个name变量未赋值,它输出undefined

同样,当我们希望将一个方法(此方法执行后还会返回一个值)作为props传递给下层组件调用时,可能有这两种场景:

let fn = function(a){
return a;
}; // 假设传递了
let a = fn?.(1);
console.log(a);//1 // 假设没传
let func = undefined;
let b = func?.(1);
console.log(b);// undefined

你会发现使用?.调用一个可能传递的方法也非常简单,而假设这个方法没传递,函数的返回值也是undefined,这一点与上面对象属性获取的例子结果保持一致,总结来说,当引用为空(null或者undefined)的情况下不会引起错误,且表达式短路返回值是 undefined

叁 ❀ 四种常用语法

?.支持如下几种语法:

obj?.prop//读取对象某个属性
obj?.[expr]//读取某个属性,且这个属性名还是个变量
arr?.[index]//读取数组下标
fn?.(args)// 调用一个可能没传递函数

我们来给几个例子感受下:

// 读取某个对象的属性
let echo = {name:'听风是风'};
let name = echo?.name; // 读取某个对象的属性,属性是个变量
let n = 'name';
let name1 = echo?.[n]; // 读取数组下标
let arr = ['echo', '时间跳跃', '听风是风'];
let name2 = arr?.[2]; // 执行某个可能不存在的函数
let fn = a => a;
let name3 = fn?.('听风是风');

另外,官方给了一个比较特殊的例子,猜猜此时a是多少?

let a = 1?.3:2;
a//?

我们知道,常规小数比如0.1可以简写成.1,其实上述代码格式化一下就是一个三元运算符:

let a = 1 ? .3 : 2;

由于1为真,所以a最终复制是0.3,专门提出这个例子是想告诉大家不要对于此场景有所疑惑,javascript还是能够正确解析的,当然,为了可读性,大家也可以通过圆括号进行范围划分:

let a = 1?(.3):2;

叁 ❀ 空值合并运算符??

叁 ❀ 壹 概念与用法

在日常聊天中,对于表示惊叹,无语,反问,大家可能会回复多个问号作为万能的答复,比如:

而在javascript世界中,它的含义不仅限于如此,在ECMAScript2020中除了推出可选链操作符外还有一个也非常好用的空值合并运算符,它就是用两个英文??表示,概念上也非常简单,当一个表达式左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

我不知道大家有没有写过如下类似的代码:

let user = {};
let name = user.name || 'echo';

这段代码的本意的是,当user.name不存在时,我们期望给name设置一个默认值,便于后续程序能正常运行。但实际开发中往往会遇到如下类似的情况,比如:

// 用户传递的就是0
let num = 0;
let num_ = num || 1; // 用户传递的就是空字符串
let str = '';
let str_ = str || 'echo';

以上就是两种比较常见的场景,用户期望就是传递一个数字0或者一个空字符,但对于短路逻辑||而言左侧是一个假值,这会导致最终复制会以右边为准,从而产生了一个bug。

而空值合并运算符??正是用于解决这一问题场景,让我换个写法:

// 用户传递的就是0
let num = 0;
let num_ = num ?? 1; // 用户传递的就是空字符串
let str = '';
let str_ = str ?? 'echo';
console.log(num_, str_);// 0 ''

你看,只有当左侧为undefined或者null时,右边的值才会生效。

贰 ❀ 注意事项

需要注意的是,空值合并运算符不能与&&或者||直接共用,如下写法都是错误的:

null || undefined ?? "echo";// 报错
true && undefined ?? "echo";// 报错

对于javascript而言,它根本不知道应该怎么读取这段代码,比较好的做法还是通过圆括号进行范围划分,这样解析器才知道那一小块是一个整体,比如:

let name = (null || undefined) ?? "echo";
let age = (true && undefined) ?? 28;//28

OK,空值合并运算符相对而言简单不少,那么就介绍到这里。

PS:由于ECMAScript2020概念相对来说还比较新,部分浏览器低版本并不支持,请确认项目中有使用babel做类似ES5转义,不然大概率会出现浏览器不兼容的语法报错,那么到这里两个JS小技巧介绍完毕,本文结束。

参考

Optional Chaining for JavaScript

Nullish Coalescing for JavaScript

可选链操作符 - JavaScript | MDN

空值合并运算符 - JavaScript | MDN

JS 可选链操作符?. 空值合并运算符?? 详解,更精简的安全取值与默认值设置小技巧的更多相关文章

  1. js原型链继承的傻瓜式详解

    本文争取用最简单的语言来讲解原型链继承的OOP原理 0.如果对原型继承还没有大致了解,完全一头雾水,请先阅读 <JavaScript高级程序设计>第六章最后部分的寄生组合式继承 或者_廖雪 ...

  2. js 可选链 & 空值合并 In Action

    js 可选链 & 空值合并 In Action const obj = { props: { name: 'eric', }, // prop, 不存在的属性 ️ }; console.log ...

  3. 可选链运算符、空值合并运算符 --应用到vue项目

    1.npm安装 npm install @babel/plugin-proposal-optional-chaining // 可选链运算符 ?. npm install @babel/plugin- ...

  4. ?.可选链操作符( ?. ) 可选链运算符可防止抛出 TypeError: Cannot read property ’xxx' of undefined。

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

  5. Net is as typeof 运行运算符详解 net 自定义泛型那点事

    Net is as typeof 运行运算符详解   概述 在了解运行运算符的前提我们需要了解什么是RTTI ,在任何一门面向对象的语言中,都有RTTI这个概念(即 运行时). RTTI(Run-Ti ...

  6. js正则实现二代身份证号码验证详解

    js正则实现二代身份证号码验证详解 根据[中华人民共和国国家标准 GB 11643-1999]中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成.排列顺序从左至 ...

  7. 减少HTTP请求之合并图片详解(大型网站优化技术)

    原文:减少HTTP请求之合并图片详解(大型网站优化技术) 一.相关知识讲解 看过雅虎的前端优化35条建议,都知道优化前端是有多么重要.页面的加载速度直接影响到用户的体验.80%的终端用户响应时间都花在 ...

  8. vue.js选择if(条件渲染)详解

    vue.js选择if(条件渲染)详解 一.总结 一句话总结: v-if <!DOCTYPE html> <html lang="en"> <head& ...

  9. vue.js循环for(列表渲染)详解

    vue.js循环for(列表渲染)详解 一.总结 一句话总结: v-for <ul id="example-1"> <li v-for="item in ...

  10. Angular.js中处理页面闪烁的方法详解

    Angular.js中处理页面闪烁的方法详解 前言 大家在使用{{}}绑定数据的时候,页面加载会出现满屏尽是{{xxx}}的情况.数据还没响应,但页面已经渲染了.这是因为浏览器和angularjs渲染 ...

随机推荐

  1. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2024.01.21)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  2. 通过宿主机查看K8S或者是容器内的Java程序的简单方法

    通过宿主机查看K8S或者是容器内的Java程序的简单方法 背景 最近一个项目的环境出现了 cannot create native process 的错误提示 出现这个错误提示时, docker ex ...

  3. 人大金仓学习之四-kmonitor

    人大金仓学习之四-kmonitor 背景 kmonitor 其实时一个很好的工具和思路 开元的软件封装一下, 减轻技术复杂度,提高部署效率 并且能够更加快速的定位解决问题. 能够极大的提升客户体验. ...

  4. iperf的学习与部分网络状况的简要总结

    背景 随着信息安全的越来越重要,公司要求进行数据备份. 部分客户现场交付之前需要进行性能压测,但是因为各种环境问题效果不是很理想. 前段时间疫情严重,经常需要居家办公,出现了很多网络相关的问题. 因为 ...

  5. vue面试题(一)正在重新整理

    1.输入一个 URL到浏览器整个过程发生了什么?ok 1.浏览器查找当前 URL是否存有缓存,并检查这个缓存是否过期 2.DNS 解析 URL 对应的 IP 3.根据 IP 建立 TCP 连接(三次握 ...

  6. 如何写RN样式 如何写RN组件 如何满屏 如何使用变量

    app.js 文本水平居中了哈 控制文本的大小 字体颜色等 只有在文本元素上去控制哈 import React from 'react'; import {View, Text, StyleSheet ...

  7. consul系列文章01---docker安装consul

    1.下载镜像 2.运行容器 docker run --name consul -d -p 8500:8500 --restart=always  consul agent -server -boots ...

  8. #region在多种编程语言及IDE中进行代码折叠,包括python msvc++ c#等

    vs/rider中折叠C#代码 在写C#的时候,在visual studio中可以使用#region和#endregion来进行代码折叠,那么在pycharm中是否可以呢? //这里有很多的代码... ...

  9. 小白学k8s(3)什么是内网穿透

    什么是内网穿透 内网穿透 工作方式 通信的一方处于内网 通信的双方都处于内网 NAT穿透的原理 UDP内网穿透的实现流程 参考 什么是内网穿透 内网穿透 什么是内网穿透呢? 百度百科的描述 内网穿透, ...

  10. 知识蒸馏相关技术【模型蒸馏、数据蒸馏】以ERNIE-Tiny为例

    1.任务简介 基于ERNIE预训练模型效果上达到业界领先,但是由于模型比较大,预测性能可能无法满足上线需求. 直接使用ERNIE-Tiny系列轻量模型fine-tune,效果可能不够理想.如果采用数据 ...