這些 JS 中強大的操作符,總有幾個你沒聽說過

SHERlocked93發表於2021-01-19

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  ^: 1010
   0110     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
運算子示例說明
&&expr1&&expr2如果 expr1 能轉換成 false 則返回 expr1,否則返回 expr2。 因此,在 Boolean 環境中使用時, 兩個操作結果都為 true 時返回 true,否則返回 false
||expr1||expr2如果 expr1 能轉換成 true 則返回 expr1,否則返回 expr2。 因此,在 boolean 環境(在if的條件判斷中)中使用時, 二者操作結果中只要有一個為 true,返回 true;二者操作結果都為 false 時返回 false
!!expr如果單個表示式能轉換為 true 的話返回 false,否則返回 true

因此可以用來做很多有意思的事,比如給變數賦初值:

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)排列,但這個不是最新的,至少沒包括可選鏈,建議參考這個表或者 MDN

運算子優先順序


網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出,如果本文幫助到了你,別忘了點贊支援一下哦,你的點贊是我更新的最大動力!(收藏不點贊,都是耍流氓 ?)~

參考文件:

  1. 運算子優先順序 - JavaScript | MDN
  2. JS 中可以提升幸福度的小技巧
  3. 4個未聽說過的強大JavaScript操作符
  4. 聊聊JavaScript中的二進位制數

PS:本文收錄在在下的部落格 Github - SHERlocked93/blog 系列文章中,歡迎大家關注我的公眾號 前端下午茶,直接搜尋即可新增或者點這裡新增,持續為大家推送前端以及前端周邊相關優質技術文,共同進步,一起加油~

相關文章