大家好,我是若川。今天推薦一篇相對簡單些的文章。
大家應該都知道了我最近組織了源碼共讀活動, 有小伙伴表示讀源碼上癮,也很有收獲。工作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
, ''
, NaN
, null
, undefined
)都不會被返回。這導致如果你使用 0
、''
、NaN
作為有效值,就會出現不可預料的后果。
正因為 ||
存在這樣的問題,而 ??
的出現就是解決了這些問題,??
只會在左側為 undefined
、null
時才返回后者,??
可以理解為是 ||
的完善解決方案。
可以在瀏覽器中執行下面的代碼感受一下:
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
這個假值,這樣在某些地方防止 param
為 undefined
的時候還取其屬性造成報錯。
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. 其他常用操作符
三元表達式:很簡單了,大家經常用,
expr ? expr1 : expr2
如果expr
為真值則返回expr1
,否則返回expr2
賦值運算符簡寫:加法賦值
+=
、減法賦值-=
、乘法賦值*=
、除法賦值/=
、求冪賦值**=
、按位或復制|=
、按位與賦值&=
、有符號按位右移賦值>>=
、無符號按位右移賦值>>>=
、邏輯空賦值??=
....求冪運算符:
var1 ** var2
相當于Math.pow
,結果為var1
的var2
次方
12. 操作符優先級
正因為有操作符優先級,所以 variable = 1, 2
的含義是將變量先賦值為 1,再返回數字 2,而不是變量賦值給 1, 2
的返回值 2,這是因為 =
運算符的優先級高于 ,
逗號運算符。再比如表達式 6 - 2 * 3 === 0 && 1
,- * === &&
這四個運算符優先級最高的 *
先運算,然后 -
運算符結果為 0,===
運算符優先級高于 &&
而 true && 1
的結果為 1,所以這就是運算的結果。
下面的表將運算符按照優先級的不同從高(20)到低(1)排列,但這個不是最新的,至少沒包括可選鏈,建議參考這個表[1]或者 MDN[2]。

參考文檔:
運算符優先級 - JavaScript | MDN[3]
JS 中可以提升幸福度的小技巧[4]
4個未聽說過的強大JavaScript操作符
聊聊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年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~