1.指數(shù)運算符
ES2016 新增了一個指數(shù)運算符(**
)妓湘。
2 ** 2 // 4
2 ** 3 // 8
這個運算符的一個特點是右結(jié)合鞍历,而不是常見的左結(jié)合。多個指數(shù)運算符連用時背蟆,是從最右邊開始計算的鉴分。
// 相當(dāng)于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
上面代碼中哮幢,首先計算的是第二個指數(shù)運算符,而不是第一個志珍。
指數(shù)運算符可以與等號結(jié)合橙垢,形成一個新的賦值運算符(**=
)。
let a = 1.5;
a **= 2;
// 等同于 a = a * a;
let b = 4;
b **= 3;
// 等同于 b = b * b * b;
2.鏈判斷運算符
編程中伦糯,如果讀取對象內(nèi)部的某個屬性柜某,往往需要判斷一下,屬性的上層對象是否存在敛纲。比如喂击,讀取message.body.user.firstName
這個屬性,安全的寫法是寫成下面這樣淤翔。
// 錯誤的寫法
const firstName = message.body.user.firstName || 'default';
// 正確的寫法
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
上面例子中翰绊,firstName
屬性在對象的第四層,所以需要判斷四次旁壮,每一層是否有值监嗜。
三元運算符?:
也常用于判斷對象是否存在。
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
上面例子中寡具,必須先判斷fooInput
是否存在秤茅,才能讀取fooInput.value
。
這樣的層層判斷非常麻煩童叠,因此 ES2020 引入了“鏈判斷運算符”(optional chaining operator)?.
,簡化上面的寫法课幕。
const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
上面代碼使用了?.
運算符厦坛,直接在鏈式調(diào)用的時候判斷,左側(cè)的對象是否為null
或undefined
乍惊。如果是的杜秸,就不再往下運算,而是返回undefined
润绎。
下面是判斷對象方法是否存在铅忿,如果存在就立即執(zhí)行的例子喂江。
iterator.return?.()
上面代碼中,iterator.return
如果有定義,就會調(diào)用該方法譬胎,否則iterator.return
直接返回undefined
,不再執(zhí)行?.
后面的部分钦讳。
對于那些可能沒有實現(xiàn)的方法鲫剿,這個運算符尤其有用。
if (myForm.checkValidity?.() === false) {
// 表單校驗失敗
return;
}
上面代碼中涂佃,老式瀏覽器的表單對象可能沒有checkValidity()
這個方法励翼,這時?.
運算符就會返回undefined
蜈敢,判斷語句就變成了undefined === false
,所以就會跳過下面的代碼汽抚。
鏈判斷運算符?.
有三種寫法抓狭。
-
obj?.prop
// 對象屬性是否存在 -
obj?.[expr]
// 同上 -
func?.(...args)
// 函數(shù)或?qū)ο蠓椒ㄊ欠翊嬖?/li>
下面是obj?.[expr]
用法的一個例子。
let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1];
上面例子中造烁,字符串的match()
方法辐宾,如果沒有發(fā)現(xiàn)匹配會返回null
,如果發(fā)現(xiàn)匹配會返回一個數(shù)組膨蛮,?.
運算符起到了判斷作用叠纹。
下面是?.
運算符常見形式,以及不使用該運算符時的等價形式敞葛。
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
上面代碼中誉察,特別注意后兩種形式,如果a?.b()
和a?.()
惹谐。如果a?.b()
里面的a.b
有值持偏,但不是函數(shù),不可調(diào)用氨肌,那么a?.b()
是會報錯的鸿秆。a?.()
也是如此,如果a
不是null
或undefined
怎囚,但也不是函數(shù)卿叽,那么a?.()
會報錯。
使用這個運算符恳守,有幾個注意點考婴。
(1)短路機制
本質(zhì)上,?.
運算符相當(dāng)于一種短路機制催烘,只要不滿足條件沥阱,就不再往下執(zhí)行。
a?.[++x]
// 等同于
a == null ? undefined : a[++x]
上面代碼中伊群,如果a
是undefined
或null
考杉,那么x
不會進行遞增運算。也就是說舰始,鏈判斷運算符一旦為真崇棠,右側(cè)的表達式就不再求值。
(2)括號的影響
如果屬性鏈有圓括號蔽午,鏈判斷運算符對圓括號外部沒有影響易茬,只對圓括號內(nèi)部有影響。
(a?.b).c
// 等價于
(a == null ? undefined : a.b).c
上面代碼中,?.
對圓括號外部沒有影響抽莱,不管a
對象是否存在范抓,圓括號后面的.c
總是會執(zhí)行。
一般來說食铐,使用?.
運算符的場合匕垫,不應(yīng)該使用圓括號,否則失去了鏈判斷運算的意義虐呻。
(3)報錯場合
以下寫法是禁止的象泵,會報錯。
// 構(gòu)造函數(shù)
new a?.()
new a?.b()
// 鏈判斷運算符的右側(cè)有模板字符串
a?.`斟叼`
a?.b`{c}`
// 鏈判斷運算符的左側(cè)是 super
super?.()
super?.foo
// 鏈運算符用于賦值運算符左側(cè)
a?.b = c
(4)右側(cè)不得為十進制數(shù)值
為了保證兼容以前的代碼偶惠,允許foo?.3:0
被解析成foo ? .3 : 0
,因此規(guī)定如果?.
后面緊跟一個十進制數(shù)字朗涩,那么?.
不再被看成是一個完整的運算符忽孽,而會按照三元運算符進行處理,也就是說谢床,那個小數(shù)點會歸屬于后面的十進制數(shù)字兄一,形成一個小數(shù)。
3.Null 判斷運算符
讀取對象屬性的時候识腿,如果某個屬性的值是null
或undefined
出革,有時候需要為它們指定默認值。常見做法是通過||
運算符指定默認值渡讼。
const headerText = response.settings.headerText || 'Hello, world!';
const animationDuration = response.settings.animationDuration || 300;
const showSplashScreen = response.settings.showSplashScreen || true;
上面的三行代碼都通過||
運算符指定默認值骂束,但是這樣寫是錯的。開發(fā)者的原意是硝全,只要屬性的值為null
或undefined
栖雾,默認值就會生效,但是屬性的值如果為空字符串或false
或0
伟众,默認值也會生效。
為了避免這種情況召廷,ES2020 引入了一個新的 Null 判斷運算符??
凳厢。它的行為類似||
,但是只有運算符左側(cè)的值為null
或undefined
時竞慢,才會返回右側(cè)的值先紫。
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
上面代碼中,默認值只有在左側(cè)屬性值為null
或undefined
時筹煮,才會生效遮精。
這個運算符的一個目的,就是跟鏈判斷運算符?.
配合使用,為null
或undefined
的值設(shè)置默認值本冲。
const animationDuration = response.settings?.animationDuration ?? 300;
上面代碼中准脂,如果response.settings
是null
或undefined
,或者response.settings.animationDuration
是null
或undefined
檬洞,就會返回默認值300狸膏。也就是說,這一行代碼包括了兩級屬性的判斷添怔。
這個運算符很適合判斷函數(shù)參數(shù)是否賦值湾戳。
function Component(props) {
const enable = props.enabled ?? true;
// …
}
上面代碼判斷props
參數(shù)的enabled
屬性是否賦值,基本等同于下面的寫法广料。
function Component(props) {
const {
enabled: enable = true,
} = props;
// …
}
??
本質(zhì)上是邏輯運算砾脑,它與其他兩個邏輯運算符&&
和||
有一個優(yōu)先級問題,它們之間的優(yōu)先級到底孰高孰低艾杏。優(yōu)先級的不同韧衣,往往會導(dǎo)致邏輯運算的結(jié)果不同。
現(xiàn)在的規(guī)則是糜颠,如果多個邏輯運算符一起使用汹族,必須用括號表明優(yōu)先級,否則會報錯其兴。
// 報錯
lhs && middle ?? rhs
lhs ?? middle && rhs
lhs || middle ?? rhs
lhs ?? middle || rhs
上面四個表達式都會報錯顶瞒,必須加入表明優(yōu)先級的括號。
(lhs && middle) ?? rhs;
lhs && (middle ?? rhs);
(lhs ?? middle) && rhs;
lhs ?? (middle && rhs);
(lhs || middle) ?? rhs;
lhs || (middle ?? rhs);
(lhs ?? middle) || rhs;
lhs ?? (middle || rhs);
4.邏輯賦值運算符
ES2021 引入了三個新的邏輯賦值運算符(logical assignment operators)元旬,將邏輯運算符與賦值運算符進行結(jié)合榴徐。
// 或賦值運算符
x ||= y
// 等同于
x || (x = y)
// 與賦值運算符
x &&= y
// 等同于
x && (x = y)
// Null 賦值運算符
x ??= y
// 等同于
x ?? (x = y)
這三個運算符||=
、&&=
匀归、??=
相當(dāng)于先進行邏輯運算坑资,然后根據(jù)運算結(jié)果,再視情況進行賦值運算穆端。
它們的一個用途是袱贮,為變量或?qū)傩栽O(shè)置默認值。
// 老的寫法
user.id = user.id || 1;
// 新的寫法
user.id ||= 1;
上面示例中体啰,user.id
屬性如果不存在攒巍,則設(shè)為1
,新的寫法比老的寫法更緊湊一些荒勇。
下面是另一個例子柒莉。
function example(opts) {
opts.foo = opts.foo ?? 'bar';
opts.baz ?? (opts.baz = 'qux');
}
上面示例中,參數(shù)對象opts
如果不存在屬性foo
和屬性baz
沽翔,則為這兩個屬性設(shè)置默認值兢孝。有了“Null 賦值運算符”以后,就可以統(tǒng)一寫成下面這樣。
function example(opts) {
opts.foo ??= 'bar';
opts.baz ??= 'qux';
}
5.#!命令
Unix 的命令行腳本都支持#!
命令跨蟹,又稱為 Shebang 或 Hashbang雳殊。這個命令放在腳本的第一行,用來指定腳本的執(zhí)行器喷市。
比如 Bash 腳本的第一行相种。
#!/bin/sh
Python 腳本的第一行。
#!/usr/bin/env python
ES2023 為 JavaScript 腳本引入了#!
命令品姓,寫在腳本文件或者模塊文件的第一行寝并。
// 寫在腳本文件第一行
#!/usr/bin/env node
'use strict';
console.log(1);
// 寫在模塊文件第一行
#!/usr/bin/env node
export {};
console.log(1);
有了這一行以后,Unix 命令行就可以直接執(zhí)行腳本腹备。
# 以前執(zhí)行腳本的方式
$ node hello.js
# hashbang 的方式
$ ./hello.js
對于 JavaScript 引擎來說衬潦,會把#!
理解成注釋,忽略掉這一行植酥。