JS 里的操作符大家每天都在使用,還有一些 ES2020、ES2021 新加的實(shí)用操作符,這些共同構(gòu)成了 JS 靈活的語(yǔ)法生態(tài)。
本文除介紹常用的操作符之外,還會(huì)介紹 JS 里一些不常用但是很強(qiáng)大的操作符,下面我們一起來(lái)看看吧~
1. 數(shù)值分割符 _
ES2021 引入了數(shù)值分割符 _,在數(shù)值組之間提供分隔,使一個(gè)長(zhǎng)數(shù)值讀起來(lái)更容易。Chrome 已經(jīng)提供了對(duì)數(shù)值分割符的支持,可以在瀏覽器里試起來(lái)。
let number = 100_0000_0000_0000 // 0太多了不用數(shù)值分割符眼睛看花了
console.log(number) // 輸出 100000000000000
此外,十進(jìn)制的小數(shù)部分也可以使用數(shù)值分割符,二進(jìn)制、十六進(jìn)制里也可以使用數(shù)值分割符。
0x11_1 === 0x111 // true 十六進(jìn)制
0.11_1 === 0.111 // true 十進(jìn)制的小數(shù)
0b11_1 === 0b111 // true 二進(jìn)制
2. 逗號(hào)運(yùn)算符 ,
什么,逗號(hào)也可以是運(yùn)算符嗎?是的,曾經(jīng)看到這樣一個(gè)簡(jiǎn)單的函數(shù),將數(shù)組的第一項(xiàng)和第二項(xiàng)調(diào)換,并返回兩項(xiàng)之和:
function reverse(arr) {
return [arr[0], arr[1]]=[arr[1], arr[0]], arr[0] + arr[1]
}
const list = [1, 2]
reverse(list) // 返回 3,此時(shí) list 為[2, 1]
逗號(hào)操作符對(duì)它的每個(gè)操作數(shù)求值(從左到右),并返回最后一個(gè)操作數(shù)的值。
expr1, expr2, expr3...
會(huì)返回最后一個(gè)表達(dá)式 expr3 的結(jié)果,其他的表達(dá)式只會(huì)進(jìn)行求值。
3. 零合并操作符 ??
零合并操作符 ?? 是一個(gè)邏輯操作符,當(dāng)左側(cè)的操作數(shù)為 null 或者 undefined 時(shí),返回右側(cè)操作數(shù),否則返回左側(cè)操作數(shù)。
expr1 ?? expr2
空值合并操作符一般用來(lái)為常量提供默認(rèn)值,保證常量不為 null 或者 undefined,以前一般使用 || 來(lái)做這件事 variable = variable || 'bar'。然而,由于 || 是一個(gè)布爾邏輯運(yùn)算符,左側(cè)的操作數(shù)會(huì)被強(qiáng)制轉(zhuǎn)換成布爾值用于求值。任何假值(0, '', NaN, null, undefined)都不會(huì)被返回。這導(dǎo)致如果你使用 0、''、NaN 作為有效值,就會(huì)出現(xiàn)不可預(yù)料的后果。
正因?yàn)?/span> || 存在這樣的問(wèn)題,而 ?? 的出現(xiàn)就是解決了這些問(wèn)題,?? 只會(huì)在左側(cè)為 undefined、null 時(shí)才返回后者,?? 可以理解為是 || 的完善解決方案。
可以在瀏覽器中執(zhí)行下面的代碼感受一下:
undefined || 'default' // 'default'
null || 'default' // 'default'
false || 'default' // 'default'
0 || 'default' // 'default'
undefined ?? 'default' // 'default'
null ?? 'default' // 'default'
false ?? 'default' // 'false'
0 ?? 'default' // 0
另外在賦值的時(shí)候,可以運(yùn)用賦值運(yùn)算符的簡(jiǎn)寫(xiě) ??=
let a = {b: null, c: 10}
a.b ??= 20
a.c ??= 20
console.log(a) // 輸出 { b: 20, c: 10 }
4. 可選鏈操作符 ?.
可選鏈操作符 ?. 允許讀取位于連接對(duì)象鏈深處的屬性的值,而不必驗(yàn)證鏈中的每個(gè)引用是否有效。?. 操作符的功能類(lèi)似于 . 鏈?zhǔn)讲僮鞣?,不同之處在于,在引用?null 或者 undefined 的情況下不會(huì)引起錯(cuò)誤,該表達(dá)式短路返回值是 undefined。
當(dāng)嘗試訪問(wèn)可能不存在的對(duì)象屬性時(shí),可選鏈操作符將會(huì)使表達(dá)式更短、更簡(jiǎn)明。
const obj = {
a: 'foo',
b: {
c: 'bar'
}
}
console.log(obj.b?.c) // 輸出 bar
console.log(obj.d?.c) // 輸出 undefined
console.log(obj.func?.()) // 不報(bào)錯(cuò),輸出 undefined
以前可能會(huì)通過(guò) obj && obj.a && obj.a.b 來(lái)獲取一個(gè)深度嵌套的子屬性,現(xiàn)在可以直接 obj?.a?.b 即可。
可選鏈除了可以用在獲取對(duì)象的屬性,還可以用在數(shù)組的索引 arr?.[index],也可以用在函數(shù)的判斷 func?.(args),當(dāng)嘗試調(diào)用一個(gè)可能不存在的方法時(shí)也可以使用可選鏈。
調(diào)用一個(gè)對(duì)象上可能不存在的方法時(shí)(版本原因或者當(dāng)前用戶(hù)的設(shè)備不支持該功能的場(chǎng)景下),使用可選鏈可以使得表達(dá)式在函數(shù)不存在時(shí)返回 undefined 而不是直接拋異常。
const result = someInterface.customFunc?.()
5. 私有方法/屬性
在一個(gè)類(lèi)里面可以給屬性前面增加 # 私有標(biāo)記的方式來(lái)標(biāo)記為私有,除了屬性可以被標(biāo)記為私有外,getter/setter 也可以標(biāo)記為私有,方法也可以標(biāo)為私有。
class Person {
getDesc(){
return this.#name +' '+ this.#getAge()
}
#getAge(){ return this.#age } // 私有方法
get #name(){ return 'foo' } // 私有訪問(wèn)器
#age = 23 // 私有屬性
}
const a = new Person()
console.log(a.age) // undefined 直接訪問(wèn)不到
console.log(a.getDesc()) // foo 23
6. 位運(yùn)算符 >> 與 >>>
有符號(hào)右移操作符 >> 將第一個(gè)操作數(shù)向右移動(dòng)指定的位數(shù),多余的位移到右邊被丟棄,高位補(bǔ)其符號(hào)位,正數(shù)補(bǔ) 0,負(fù)數(shù)則補(bǔ) 1。因?yàn)樾碌淖钭笪慌c前一個(gè)最左位的值站長(zhǎng)博客相同,所以符號(hào)位(最左位)不會(huì)改變。
(0b111>>1).toString(2) // "11"
(-0b111>>1).toString(2) // "-100" 感覺(jué)跟直覺(jué)不一樣
正數(shù)的好理解,負(fù)數(shù)怎么理解呢,負(fù)數(shù)在計(jì)算機(jī)中存儲(chǔ)是按照補(bǔ)碼來(lái)存儲(chǔ)的,補(bǔ)碼的計(jì)算方式是取反加一,移位時(shí)將補(bǔ)碼形式右移,最左邊補(bǔ)符號(hào)位,移完之后再次取反加一求補(bǔ)碼獲得處理后的原碼。
-111 // 真值
1 0000111 // 原碼(高位的0無(wú)所謂,后面加不到)
1 1111001 // 補(bǔ)碼
1 1111100 // 算數(shù)右移
1 0000100 // 移位后求補(bǔ)碼獲得原碼
-100 // 移位后的真值
一般我們用 >> 來(lái)將一個(gè)數(shù)除 2,相當(dāng)于先舍棄小數(shù)位然后進(jìn)行一次 Math.floor:
10 >> 1 // 5
13 >> 1 // 6 相當(dāng)于
13.9 >> 1 // 6
-13 >> 1 // -7 相當(dāng)于
-13.9 >> 1 // -7
無(wú)符號(hào)右移操作符 >>>,將符號(hào)位作為二進(jìn)制數(shù)據(jù)的一部分向右移動(dòng),高位始終補(bǔ) 0,對(duì)于正整數(shù)和算數(shù)右移沒(méi)有區(qū)別,對(duì)于負(fù)數(shù)來(lái)說(shuō)由于符號(hào)位被補(bǔ) 0,成為正數(shù)后就不用再求補(bǔ)碼了,所以結(jié)果總是非負(fù)的。即便右移 0 個(gè)比特,結(jié)果也是非負(fù)的。
(0b111>>>1).toString(2) // "11"
(-0b111>>>1).toString(2) // "1111111111111111111111111111100"
可以這樣去理解
-111 // 真值
1 000000000000000000000000000111 // 原碼
1 111111111111111111111111111001 // 補(bǔ)碼
0 111111111111111111111111111100 // 算數(shù)右移(由于右移后成為正數(shù),就不要再求補(bǔ)碼了)
1073741820 // 移位后的真值
左移運(yùn)算符 << 與之類(lèi)似,左移很簡(jiǎn)單左邊移除最高位,低位補(bǔ) 0:
(0b1111111111111111111111111111100<<1).toString(2) // "-1000"
(0b1111111111111111111111111111100<<<1).toString(2) // "-1000"
PS:JS 里面沒(méi)有無(wú)符號(hào)左移,而且其他語(yǔ)言比如 JAVA 也沒(méi)有無(wú)符號(hào)左移。
7. 位運(yùn)算符 & 與 |
位運(yùn)算符是按位進(jìn)行運(yùn)算,& 與、| 或、~ 非、^ 按位異或:
&: 1010 |: 1010 ~: 1010 ^: 1010
0110 0110 0110
---- ---- ---- ----
0010 1110 0101 1100
使用位運(yùn)算符時(shí)會(huì)拋棄小數(shù)位,我們可以利用這個(gè)特性來(lái)給數(shù)字取整,比如給任意數(shù)字 & 上二進(jìn)制的 32 個(gè) 1,或者 | 上 0,顯而易見(jiàn)后者簡(jiǎn)單些。
所以我們可以對(duì)一個(gè)數(shù)字 | 0 來(lái)取整,負(fù)數(shù)也同樣適用
1.3 | 0 // 1
-1.9 | 0 // -1
判斷奇偶數(shù)除了常見(jiàn)的取余 % 2 之外,也可以使用 & 1,來(lái)判斷二進(jìn)制數(shù)的最低位是不是 1,這樣除了最低位之外都被置 0,取余的結(jié)果只剩最低位,是不是很巧妙。負(fù)數(shù)也同樣適用:
const num = 3
!!(num & 1) // true
!!(num % 2) // true
8. 雙位運(yùn)算符 ~~
可以使用雙位操作符來(lái)替代正數(shù)的 Math.floor( ),替代負(fù)數(shù)的 Math.ceil( )。雙否定位操作符的優(yōu)勢(shì)在于它執(zhí)行相同的操作運(yùn)行速度更快。
Math.floor(4.9) === 4 // true
// 簡(jiǎn)寫(xiě)為:
~~4.9 === 4 // true
不過(guò)要注意,對(duì)正數(shù)來(lái)說(shuō) ~~ 運(yùn)算結(jié)果與 Math.floor( ) 運(yùn)算結(jié)果相同,而對(duì)于負(fù)數(shù)來(lái)說(shuō)與 Math.ceil( ) 的運(yùn)算結(jié)果相同:
~~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 在值為負(fù)數(shù)時(shí)的差別
9. 短路運(yùn)算符 && 與 ||
我們知道邏輯與 && 與邏輯或 || 是短路運(yùn)算符,短路運(yùn)算符就是從左到右的運(yùn)算中前者滿(mǎn)足要求,就不再執(zhí)行后者了。
可以理解為:
&& 為取假運(yùn)算,從左到右依次判斷,如果遇到一個(gè)假值,就返回假值,以后不再執(zhí)行,否則返回最后一個(gè)真值
|| 為取真運(yùn)算,從左到右依次判斷,如果遇到一個(gè)真值,就返回真值,以后不再執(zhí)行,否則返回最后一個(gè)假值
let param1 = expr1 && expr2
let param2 = expr1 || expr2
短路運(yùn)算符
因此可以用來(lái)做很多有意思的事,比如給變量賦初值:
let variable1
let variable2 = variable1 || 'foo'
如果 variable1 是真值就直接返回了,后面短路就不會(huì)被返回了,如果為假值,則會(huì)返回后面的foo。
也可以用來(lái)進(jìn)行簡(jiǎn)單的判斷,取代冗長(zhǎng)的if語(yǔ)句:
let variable = param && param.prop
// 有了可選鏈之后可以直接 param?.prop
如果 param 如果為真值則返回 param.prop 屬性,否則返回 param 這個(gè)假值,這樣在某些地方防止 param 為 undefined 的時(shí)候還取其屬性造成報(bào)錯(cuò)。
10. void 運(yùn)算符
void 運(yùn)算符 對(duì)給定的表達(dá)式進(jìn)行求值,然后返回 undefined
可以用來(lái)給在使用立即調(diào)用的函數(shù)表達(dá)式(IIFE)時(shí),可以利用 void 運(yùn)算符讓 JS 引擎把一個(gè) function 關(guān)鍵字識(shí)別成函數(shù)表達(dá)式而不是函數(shù)聲明。
function iife() { console.log('foo') }() // 報(bào)錯(cuò),因?yàn)镴S引擎把IIFE識(shí)別為了函數(shù)聲明
void function iife() { console.log('foo') }() // 正常調(diào)用
~function iife() { console.log('foo') }() // 也可以使用一個(gè)位操作符
(function iife() { console.log('foo') })() // 或者干脆用括號(hào)括起來(lái)表示為整體的表達(dá)式
還可以用在箭頭函數(shù)中避免傳值泄漏,箭頭函數(shù),允許在函數(shù)體不使用括號(hào)來(lái)直接返回值。這個(gè)特性給用戶(hù)帶來(lái)了很多便利,但有時(shí)候也帶來(lái)了不必要的麻煩,如果右側(cè)調(diào)用了一個(gè)原本沒(méi)有返回值的函數(shù),其返回值改變后,會(huì)導(dǎo)致非預(yù)期的副作用。
const func = () => void customMethod() // 特別是給一個(gè)事件或者回調(diào)函數(shù)傳一個(gè)函數(shù)時(shí)
安全起見(jiàn),當(dāng)不希望函數(shù)返回值是除了空值以外其他值,應(yīng)該使用 void 來(lái)確保返回 undefined,這樣,當(dāng) customMethod 返回值發(fā)生改變時(shí),也不會(huì)影響箭頭函數(shù)的行為。
11. 其他常用操作符
三元表達(dá)式:很簡(jiǎn)單了,大家經(jīng)常用, expr ? expr1 : expr2 如果 expr 為真值則返回 expr1,否則返回 expr2
賦值運(yùn)算符簡(jiǎn)寫(xiě):加法賦值 +=、減法賦值 -=、乘法賦值 *=、除法賦值 /=、求冪賦值 **=、按位或復(fù)制 |=、按位與賦值 &=、有符號(hào)按位右移賦值 >>=、無(wú)符號(hào)按位右移賦值 >>>=、邏輯空賦值 ??= ....
求冪運(yùn)算符: var1 ** var2 相當(dāng)于 Math.pow,結(jié)果為 var1 的 var2 次方
12. 操作符優(yōu)先級(jí)
正因?yàn)橛胁僮鞣麅?yōu)先級(jí),所以 variable = 1, 2 的含義是將變量先賦值為 1,再返回?cái)?shù)字 2,而不是變量賦值給 1, 2 的返回值 2,這是因?yàn)?= 運(yùn)算符的優(yōu)先級(jí)高于 , 逗號(hào)運(yùn)算符。再比如表達(dá)式 6 - 2 * 3 === 0 && 1,- * === && 這四個(gè)運(yùn)算符優(yōu)先級(jí)最高的 * 先運(yùn)算,然后 - 運(yùn)算符結(jié)果為 0,=== 運(yùn)算符優(yōu)先級(jí)高于 && 而 true && 1 的結(jié)果為 1,所以這就是運(yùn)算的結(jié)果。
下面的表將運(yùn)算符按照優(yōu)先級(jí)的不同從高(20)到低(1)排列,但這個(gè)不是最新的,至少?zèng)]包括可選鏈,建議參考這個(gè)表[1]或者 MDN[2]。
聯(lián)系客服