大家好,我是若川。今天推荐一篇相对简单些的文章。

大家应该都知道了我最近组织了源码共读活动, 有小伙伴表示读源码上瘾,也很有收获。工作0-5年都可以参与。感兴趣可以加我微信 ruochuan12 私信 源码 进群。


1. 数值分割符 _

2. 逗号运算符 ,

3. 零合并操作符 ??

4. 可选链操作符 ?.

5. 私有方法/属性

6. 位运算符 >> 与 >>>

7. 位运算符 & 与 |

8. 双位运算符 ~~

9. 短路运算符 && 与 ||

10. void 运算符

11. 其他常用操作符

12. 操作符优先级

JS 里的操作符大家每天都在使用,还有一些 ES2020、ES2021 新加的实用操作符,这些共同构成了 JS 灵活的语法生态。

本文除介绍常用的操作符之外,还会介绍 JS 里一些不常用但是很强大的操作符,下面我们一起来看看吧~

1. 数值分割符 _

ES2021 引入了数值分割符 _,在数值组之间提供分隔,使一个长数值读起来更容易。Chrome 已经提供了对数值分割符的支持,可以在浏览器里试起来。

let number = 100_0000_0000_0000 // 0太多了不用数值分割符眼睛看花了
console.log(number)             // 输出 100000000000000

此外,十进制的小数部分也可以使用数值分割符,二进制、十六进制里也可以使用数值分割符。

0x11_1 === 0x111   // true 十六进制
0.11_1 === 0.111   // true 十进制的小数
0b11_1 === 0b111   // true 二进制

2. 逗号运算符 ,

什么,逗号也可以是运算符吗?是的,曾经看到这样一个简单的函数,将数组的第一项和第二项调换,并返回两项之和:

function reverse(arr) {return [arr[0], arr[1]]=[arr[1], arr[0]], arr[0] + arr[1]
}
const list = [1, 2]
reverse(list)   // 返回 3,此时 list 为[2, 1]

逗号操作符对它的每个操作数求值(从左到右),并返回最后一个操作数的值。

expr1, expr2, expr3...

会返回最后一个表达式 expr3 的结果,其他的表达式只会进行求值。

3. 零合并操作符 ??

零合并操作符 ?? 是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回右侧操作数,否则返回左侧操作数。

expr1 ?? expr2

空值合并操作符一般用来为常量提供默认值,保证常量不为 null 或者 undefined,以前一般使用 || 来做这件事 variable = variable || 'bar'。然而,由于 || 是一个布尔逻辑运算符,左侧的操作数会被强制转换成布尔值用于求值。任何假值(0''NaNnullundefined)都不会被返回。这导致如果你使用 0''NaN 作为有效值,就会出现不可预料的后果。

正因为 || 存在这样的问题,而 ?? 的出现就是解决了这些问题,?? 只会在左侧为 undefinednull 时才返回后者,?? 可以理解为是 || 的完善解决方案。

可以在浏览器中执行下面的代码感受一下:

undefined || 'default' // 'default'
null || 'default'      // 'default'
false || 'default'     // 'default'
0 || 'default'         // 'default'undefined ?? 'default' // 'default'
null ?? 'default'      // 'default'
false ?? 'default'     // 'false'
0 ?? 'default'         // 0

另外在赋值的时候,可以运用赋值运算符的简写 ??=

let a = {b: null, c: 10}
a.b ??= 20
a.c ??= 20
console.log(a)     // 输出 { b: 20, c: 10 }

4. 可选链操作符 ?.

可选链操作符 ?. 允许读取位于连接对象链深处的属性的值,而不必验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为 null 或者 undefined 的情况下不会引起错误,该表达式短路返回值是 undefined

当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。

const obj = {a: 'foo',b: {c: 'bar'}
}console.log(obj.b?.c)      // 输出 bar
console.log(obj.d?.c)      // 输出 undefined
console.log(obj.func?.())  // 不报错,输出 undefined

以前可能会通过 obj && obj.a && obj.a.b 来获取一个深度嵌套的子属性,现在可以直接 obj?.a?.b 即可。

可选链除了可以用在获取对象的属性,还可以用在数组的索引 arr?.[index],也可以用在函数的判断 func?.(args),当尝试调用一个可能不存在的方法时也可以使用可选链。

调用一个对象上可能不存在的方法时(版本原因或者当前用户的设备不支持该功能的场景下),使用可选链可以使得表达式在函数不存在时返回 undefined 而不是直接抛异常。

const result = someInterface.customFunc?.()

5. 私有方法/属性

在一个类里面可以给属性前面增加 # 私有标记的方式来标记为私有,除了属性可以被标记为私有外,getter/setter 也可以标记为私有,方法也可以标为私有。

class Person {getDesc(){ return this.#name +' '+ this.#getAge()}#getAge(){ return this.#age } // 私有方法get #name(){ return 'foo' } // 私有访问器#age = 23                   // 私有属性
}
const a = new Person()
console.log(a.age)       // undefined 直接访问不到
console.log(a.getDesc()) // foo 23

6. 位运算符 >> 与 >>>

有符号右移操作符 >> 将第一个操作数向右移动指定的位数,多余的位移到右边被丢弃,高位补其符号位,正数补 0,负数则补 1。因为新的最左位与前一个最左位的值相同,所以符号位(最左位)不会改变。

(0b111>>1).toString(2)   // "11"
(-0b111>>1).toString(2)  // "-100" 感觉跟直觉不一样

正数的好理解,负数怎么理解呢,负数在计算机中存储是按照补码来存储的,补码的计算方式是取反加一,移位时将补码形式右移,最左边补符号位,移完之后再次取反加一求补码获得处理后的原码。

-111      // 真值
1 0000111 // 原码(高位的0无所谓,后面加不到)
1 1111001 // 补码
1 1111100 // 算数右移
1 0000100 // 移位后求补码获得原码
-100      // 移位后的真值

一般我们用 >> 来将一个数除 2,相当于先舍弃小数位然后进行一次 Math.floor

10 >> 1    // 5
13 >> 1    // 6 相当于
13.9 >> 1  // 6
-13 >> 1   // -7 相当于
-13.9 >> 1 // -7

无符号右移操作符 >>>,将符号位作为二进制数据的一部分向右移动,高位始终补 0,对于正整数和算数右移没有区别,对于负数来说由于符号位被补 0,成为正数后就不用再求补码了,所以结果总是非负的。即便右移 0 个比特,结果也是非负的。

(0b111>>>1).toString(2)   // "11"
(-0b111>>>1).toString(2)  // "1111111111111111111111111111100"

可以这样去理解

-111      // 真值
1 000000000000000000000000000111 // 原码
1 111111111111111111111111111001 // 补码
0 111111111111111111111111111100 // 算数右移(由于右移后成为正数,就不要再求补码了)
1073741820      // 移位后的真值

左移运算符 << 与之类似,左移很简单左边移除最高位,低位补 0:

(0b1111111111111111111111111111100<<1).toString(2)   // "-1000"
(0b1111111111111111111111111111100<<<1).toString(2)  // "-1000"

PS:JS 里面没有无符号左移,而且其他语言比如 JAVA 也没有无符号左移。

7. 位运算符 & 与 |

位运算符是按位进行运算,& 与、| 或、~ 非、^ 按位异或:

&: 1010  |: 1010  ~: 1010  ^: 10100110     0110              0110----     ----     ----     ----0010     1110     0101     1100

使用位运算符时会抛弃小数位,我们可以利用这个特性来给数字取整,比如给任意数字 & 上二进制的 32 个 1,或者 | 上 0,显而易见后者简单些。

所以我们可以对一个数字 | 0 来取整,负数也同样适用

1.3 | 0         // 1
-1.9 | 0        // -1

判断奇偶数除了常见的取余 % 2 之外,也可以使用 & 1,来判断二进制数的最低位是不是 1,这样除了最低位之外都被置 0,取余的结果只剩最低位,是不是很巧妙。负数也同样适用:

const num = 3
!!(num & 1)                    // true
!!(num % 2)                    // true

8. 双位运算符 ~~

可以使用双位操作符来替代正数的 Math.floor( ),替代负数的 Math.ceil( )。双否定位操作符的优势在于它执行相同的操作运行速度更快。

Math.floor(4.9) === 4      // true
// 简写为:
~~4.9 === 4      // true

不过要注意,对正数来说 ~~ 运算结果与 Math.floor( ) 运算结果相同,而对于负数来说与 Math.ceil( ) 的运算结果相同:

~~4.5                // 4
Math.floor(4.5)      // 4
Math.ceil(4.5)       // 5~~-4.5               // -4
Math.floor(-4.5)     // -5
Math.ceil(-4.5)      // -4

PS:注意 ~~(num/2) 方式和 num >> 1 在值为负数时的差别

9. 短路运算符 && 与 ||

我们知道逻辑与 && 与逻辑或 || 是短路运算符,短路运算符就是从左到右的运算中前者满足要求,就不再执行后者了。

可以理解为:

  • && 为取假运算,从左到右依次判断,如果遇到一个假值,就返回假值,以后不再执行,否则返回最后一个真值

  • || 为取真运算,从左到右依次判断,如果遇到一个真值,就返回真值,以后不再执行,否则返回最后一个假值

let param1 = expr1 && expr2
let param2 = expr1 || expr2

短路运算符

因此可以用来做很多有意思的事,比如给变量赋初值:

let variable1
let variable2 = variable1  || 'foo'

如果 variable1 是真值就直接返回了,后面短路就不会被返回了,如果为假值,则会返回后面的foo

也可以用来进行简单的判断,取代冗长的if语句:

let variable = param && param.prop
// 有了可选链之后可以直接 param?.prop

如果 param 如果为真值则返回 param.prop 属性,否则返回 param 这个假值,这样在某些地方防止 paramundefined 的时候还取其属性造成报错。

10. void 运算符

void 运算符 对给定的表达式进行求值,然后返回 undefined

可以用来给在使用立即调用的函数表达式(IIFE)时,可以利用 void 运算符让 JS 引擎把一个 function 关键字识别成函数表达式而不是函数声明。

function iife() { console.log('foo') }()       // 报错,因为JS引擎把IIFE识别为了函数声明
void function iife() { console.log('foo') }()  // 正常调用
~function iife() { console.log('foo') }()      // 也可以使用一个位操作符
(function iife() { console.log('foo') })()     // 或者干脆用括号括起来表示为整体的表达式

还可以用在箭头函数中避免传值泄漏,箭头函数,允许在函数体不使用括号来直接返回值。这个特性给用户带来了很多便利,但有时候也带来了不必要的麻烦,如果右侧调用了一个原本没有返回值的函数,其返回值改变后,会导致非预期的副作用。

const func = () => void customMethod()   // 特别是给一个事件或者回调函数传一个函数时

安全起见,当不希望函数返回值是除了空值以外其他值,应该使用 void 来确保返回 undefined,这样,当 customMethod 返回值发生改变时,也不会影响箭头函数的行为。

11. 其他常用操作符

  1. 三元表达式:很简单了,大家经常用,expr ? expr1 : expr2 如果 expr 为真值则返回 expr1,否则返回 expr2

  2. 赋值运算符简写:加法赋值 +=、减法赋值 -=、乘法赋值 *=、除法赋值 /=、求幂赋值 **=、按位或复制 |=、按位与赋值 &=、有符号按位右移赋值 >>=、无符号按位右移赋值 >>>=、逻辑空赋值 ??= ....

  3. 求幂运算符var1 ** var2 相当于 Math.pow,结果为 var1var2 次方

12. 操作符优先级

正因为有操作符优先级,所以 variable = 1, 2 的含义是将变量先赋值为 1,再返回数字 2,而不是变量赋值给 1, 2 的返回值 2,这是因为 = 运算符的优先级高于 , 逗号运算符。再比如表达式 6 - 2 * 3 === 0 && 1- * === && 这四个运算符优先级最高的 * 先运算,然后 - 运算符结果为 0,=== 运算符优先级高于 &&true && 1 的结果为 1,所以这就是运算的结果。

下面的表将运算符按照优先级的不同从高(20)到低(1)排列,但这个不是最新的,至少没包括可选链,建议参考这个表[1]或者 MDN[2]

运算符优先级

参考文档:

  1. 运算符优先级 - JavaScript | MDN[3]

  2. JS 中可以提升幸福度的小技巧[4]

  3. 4个未听说过的强大JavaScript操作符

  4. 聊聊JavaScript中的二进制数[5]

PS:本文收录在在下的博客 Github - SHERlocked93/blog[6] 系列文章中,欢迎 star~

参考资料

[1]

运算符优先级 - JavaScript | MDN: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

[2]

JS 中可以提升幸福度的小技巧: https://juejin.cn/post/6844903641468403726

[3]

聊聊JavaScript中的二进制数: https://zhuanlan.zhihu.com/p/22297104


最近组建了一个杭州的前端交流群,如果你是在杭州工作可以加我微信 ruochuan12 私信 杭州 拉你进群。


推荐阅读

我在阿里招前端,该怎么帮你(可进面试群)
我读源码的经历

面对 this 指向丢失,尤雨溪在 Vuex 源码中是怎么处理的
老姚浅谈:怎么学JavaScript?

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》多篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,活跃在知乎@若川,掘金@若川。致力于分享前端开发经验,愿景:帮助5年内前端人走向前列。

识别方二维码加我微信、拉你进源码共读

今日话题

略。欢迎分享、收藏、点赞、在看我的公众号文章~

这些 JS 中强大的操作符,总有几个你没听说过相关推荐

  1. JS中的new操作符原理解析

    JS中的new操作符原理解析 new操作符做了什么 参考 推荐阅读 你越是认真生活,你的生活就会越美好--弗兰克·劳埃德·莱特 <人生果实>经典语录 new操作符做了什么 var Pers ...

  2. JS中的位操作符:“与,或,非,异或,左移,右移”

    Javascript有算数操作符,赋值操作符,比较操作符,逻辑操作符,同时也有位操作符. 引子 先想一想,如下,该输出什么答案? console.log('5 & 1 :', ( 5 & ...

  3. js中使用new操作符做了什么事情

    例:var obj = new Base(); 该步一共做了三件事:即 var obj = {}; obj.__proto__ = Base.prototype; Base.call(obj); 第一 ...

  4. js中一些常用的基本函数

    如何使用jquery刷新当前页面 下面介绍全页面刷新方法:有时候可能会用到 window.location.reload()刷新当前页面. parent.location.reload()刷新父亲对象 ...

  5. 网站开发中JS中的常用语句

    1.document.write( " "); 输出语句 2.JS中的注释为// 3.传统的HTML文档顺序是:document- >html- >(head,body ...

  6. 彻底搞懂 JS 中 this 机制

    彻底搞懂 JS 中 this 机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.com/jasonGeng88/blog 目录 this 是什么 this 的四种绑定规 ...

  7. js中的关键字总结呢

    1.document.write(""); 输出语句 2.JS中的注释为// 3.传统的HTML文档顺序是:document->html->(head,body) 4. ...

  8. 彻底搞懂 JS 中 this 机制 1

    彻底搞懂 JS 中 this 机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.com/jasonGeng88/blog 目录 this 是什么 this 的四种绑定规 ...

  9. linux批量删除screen,你不知道的linux系统中强大的screen命令

    linux系统中强大的screen命令可能很多用户还没感受到,下面介绍的就是linux系统中强大的screen命令,具体内容如下所述. 因为做二代测序分析的原因,经常需要运行很长时间的程序.由于时间很 ...

最新文章

  1. linux查看接口名,在linux下 怎么查看网络接口的名字? 网络接口的名字英文是什么呀?...
  2. python小程序-Python基本小程序
  3. 活用变量字符串${var%%.*}
  4. 计算机更改桌面,2010年职称计算机考试:更改桌面背景和颜色
  5. 排序算法——归并排序的相关问题
  6. python发送邮件-实操记录-发送一封普通的邮箱
  7. linux dev nfc,linux下的nfc开发
  8. 如何分析风控核心报表,指标背后代表怎样的逻辑
  9. github提交代码403
  10. asp.net Forums2.0修改密码后无法登陆问题——都是Cache惹的祸
  11. 计算机组成大学考试,计算机组成原理本科期末试题4套含答案(大学期末复习资料)(21页)-原创力文档...
  12. Ubuntu18.04之apt/dpkg用法
  13. Eclipse安装svn插件的几种方式
  14. 依据余弦相似度查找常用汉字形似字
  15. Matlab求拉普拉斯变换和拉普拉斯反变换
  16. 聚类算法的原理是什么?
  17. 计算机共享找不到网络连接失败,局域网电脑无法访问共享文件网络共享失败如何解决...
  18. 如何解读肠道菌群检测报告中维生素指标
  19. U-Boot启动过程完全分析(转)
  20. 中煤集团公司硬件扩容与数据库迁移项目实施方案

热门文章

  1. wingdows安装psutil_psutil模块安装指南(win与linux)
  2. PHP调用新浪API 生成短链接
  3. 洛谷 题解 P2312 【解方程】
  4. Restful对于URL的简化
  5. 面向对象 - 继承/组合 - 总结
  6. 【BZOJ2300】【SCOI2011】糖果
  7. iOS 6 自动布局入门
  8. ARM(IMX6U)ARM Cortex-A7中断系统(GPIO按键中断驱动蜂鸣器)
  9. linux php cli 太多,【linux】php cli 处理能力到底有多强?
  10. 王者荣耀6月23服务器维护,王者荣耀6.23维护到什么时候?6月23日长枪掠火版本异常介绍...