Javascript Optional Chaining

最近看到一個(gè)ECMAScript新動(dòng)態(tài)——Optional Chaining在6月5號(hào)進(jìn)入了stage2。Stage2表明委員會(huì)已經(jīng)認(rèn)可這個(gè)新feature土童,并希望最終能加入到ECMAScript標(biāo)準(zhǔn)中去。我對(duì)Optional Chaining(以下簡(jiǎn)稱OC)還是挺感興趣的几莽,本文就借此機(jī)會(huì)談一談這個(gè)新特性秉溉。

概述

OC是一個(gè)很有名的語(yǔ)法,C#璃俗、Swift、Kotlin悉默、Ruby等很多知名語(yǔ)言都有實(shí)現(xiàn)城豁。雖然語(yǔ)義上有些許差異,不過大致方向基本相同抄课,語(yǔ)法基本都是以問號(hào)和點(diǎn)(?.)的形式表示唱星。一般來說,OC主要有以下三種使用場(chǎng)景跟磨,靜態(tài)調(diào)用间聊、動(dòng)態(tài)調(diào)用、函數(shù)調(diào)用:

obj?.prop       // optional static property access
obj?.[expr]     // optional dynamic property access
func?.(...args) // optional function or method call

當(dāng)?.前面的變量為null或是undefined時(shí)抵拘,直接返回undefined哎榴。

// undefined if `a` is null/undefined, `a.b` otherwise.
a?.b
a == null ? undefined : a.b
// undefined if `a` is null/undefined, `a[x]` otherwise.
a?.[x]
a == null ? undefined : a[x]
// undefined if `a` is null/undefined, throws a TypeError if `a.b` is not a function,
//otherwise, evaluates to `a.b()`
a?.b()
a == null ? undefined : a.b()
// undefined if `a` is null/undefined,throws a TypeError if `a` is neither null/undefined,
//nor a function invokes the function `a` otherwise
a?.()
a == null ? undefined : a()

它主要解決的問題是:當(dāng)訪問樹狀結(jié)構(gòu)的對(duì)象時(shí),需要逐個(gè)判斷中間節(jié)點(diǎn)是否有效。舉個(gè)例子尚蝌,我們想獲取地址里的街道信息迎变,但是并不確定地址本身是否存在,因此只能在獲取街道前飘言,事先判斷一下地址合法性氏豌,JS中一般有如下三種寫法:

if( address ) {
  var street = address.street;
}

var street = address ? address.street : undefined;

var street = address && address.street;

OC的寫法如下(還是能短幾個(gè)字符的):

var street = address?.street;

上面的例子比較簡(jiǎn)單,但是更深層次的結(jié)構(gòu)热凹,比如從國(guó)別泵喘、省份、城市般妙、街道一路下去尋找地址信息纪铺,每一層都需要判斷是否為undefined,這個(gè)代碼就會(huì)很惡心了碟渺。如果使用OC語(yǔ)法糖鲜锚,可以急速提高可讀性:

let street = nation?.province?.city?.street

Babel

得益于Babel的插件@babel/plugin-proposal-optional-chaining,我很早就在開發(fā)中使用OC了苫拍。

方法很簡(jiǎn)單芜繁,先安裝babel的OC插件,再在配置文件里加一行plugins即可绒极,心動(dòng)的朋友們馬上可以嘗試了骏令。

yarn add -D @babel/plugin-proposal-optional-chaining
//.babelrc
{
  "plugins": ["@babel/plugin-proposal-optional-chaining"]
}

需要注意的是:一般我們都會(huì)用eslint,事先得加上新的parserOptions垄提,不然lint會(huì)一直報(bào)錯(cuò)榔袋。

//.eslintrc.js
module.exports = {
  parserOptions: {
    parser: 'babel-eslint'
  },
  ...
}

Typescript

TS是JS的超級(jí),不過由于種種原因TS也沒實(shí)現(xiàn)這個(gè)語(yǔ)法糖铡俐。我曾經(jīng)試過在TS里用babel插件轉(zhuǎn)義OC凰兑,不過VS Code沒法支持,遂放棄审丘。
后來找了些折中的方案吏够。

  1. ramda pahtOr

    R.pathOr('N/A', ['a', 'b'], {a: {b: 2}}); //=> 2
    R.pathOr('N/A', ['a', 'b'], {c: {b: 2}}); //=> "N/A"
    

    沒用過ramda的同學(xué)可能看起來有點(diǎn)費(fèi)勁,pathOr的參數(shù)是從右到左看的滩报,等價(jià)于:

    const obj = {a: {b: 2}};
    const res = obj?.a?.b ?? 'N/A'
    

    說到這里我順便提一下??這個(gè)語(yǔ)法糖锅知,叫nullish coalescing operator,也是最近剛被提到stage2的新特性露泊,它是用來代替||的喉镰。在上面的例子里旅择,用||并且b=0的話惭笑,res = 0 || 'N/A',返回就錯(cuò)了;使用??就是來避免這類bug的沉噩。

  2. loadash _get

    loadash參數(shù)正好與ramda相反捺宗,從左到右,而且第二個(gè)參數(shù)可以是Array或String:

    _.get({ a: { b: 2 } }, ['a', 'b'], 'N/A');
    _.get({ a: { b: 2 } }, 'a.b', 'N/A');
    
  3. ts-optchain

    后來我又找到了一個(gè)更好玩的庫(kù)川蒙,只要給第一級(jí)對(duì)象包一層oc方法蚜厉,就可以一路點(diǎn)下去了;鏈路夠長(zhǎng)的話畜眨,甚至比?.語(yǔ)法糖更節(jié)省字符昼牛。

    import { oc } from 'ts-optchain';
    const obj: T = { /* ... */ };
    const value = oc(obj).propA.propB.propC(defaultValue);
    

    前提是給tsconfig.json加一個(gè)compiler plugins:

    // tsconfig.json
    {
        "compilerOptions": {
            "plugins": [
                { "transform": "ts-optchain/transform" },
            ]
        },
    }
    

邊界情況

一般開發(fā)中,我們掌握上面概述里的OC語(yǔ)法其實(shí)也夠用了康聂。不過某些場(chǎng)景下贰健,可能會(huì)出現(xiàn)一些歧義。

短路

如下恬汁,如果a不為null或undefined伶椿,x會(huì)自增。

a?.[++x]

但是a為null或undefined時(shí)氓侧,怎么辦呢脊另?應(yīng)該是x不變,理由是OC本質(zhì)上是一種語(yǔ)法糖约巷,最終會(huì)轉(zhuǎn)換為如下三元表達(dá)式偎痛,自然不會(huì)調(diào)用++x

a == null ? undefined : a[++x]

安全調(diào)用

在JS的OC里独郎,作用域僅限于調(diào)用處看彼,假如后續(xù)只用.不使用?.,調(diào)用安全是不保障的囚聚,就是說如果某一層出現(xiàn)undefined靖榕,JS會(huì)拋出異常。

a?.b.c(++x).d
a == null ? undefined : a.b.c(++x).d

也許你會(huì)覺得這個(gè)有什么好爭(zhēng)議的顽铸。但是茁计,某些語(yǔ)言(如C#、CoffeeScript)會(huì)將安全保護(hù)一路延續(xù)下去谓松;還有上面提到的ts-optchain也是這么使用的——a?.b.c(++x).d會(huì)等價(jià)于a?.b?.c?.(++x)?.d星压。對(duì)某些開發(fā)人員來說,你認(rèn)為的理所當(dāng)然可能會(huì)導(dǎo)致他人極大的困惑鬼譬。

Delete

OC是支持安全刪除的娜膘,這點(diǎn)我不是很能理解,但是委員會(huì)的解釋是:“為什么不支持呢优质?”嗯竣贪,有理有據(jù)军洼,無(wú)可辯駁。

delete a?.b
a == null ? true : delete a.b

分組

(a?.b).ca?.b.c是不一樣的演怎。

(a?.b).c
(a == null ? undefined : a.b).c
a?.b.c
a == null ? undefined : a.b.c

注意到?jīng)]匕争?括號(hào)優(yōu)先級(jí)是高于點(diǎn),在a為undefined時(shí)爷耀,解析出了undefined.c——這個(gè)會(huì)拋異常的甘桑。所以在使用OC時(shí),盡量不要添加括號(hào)歹叮,以免引起不必要的麻煩跑杭。

軼事

后來我又看了一下Q&A版塊,還是挺歡樂的咆耿。比如:

  1. 為什么語(yǔ)法是?.而不是.?
  2. 為什么null?.b的結(jié)果不是null

是不是很無(wú)聊的問題艘蹋?這是委員每天都在爭(zhēng)論的話題∑被遥看看它們的回答:

  1. .?會(huì)與三元表達(dá)式?jīng)_突女阀,比如1.?foo : bar
  2. 由于.表達(dá)式不關(guān)心.前面對(duì)象的類型,它的目的是訪問.后面的屬性屑迂,因此不會(huì)因?yàn)?code>null?.b就返回null浸策,而是統(tǒng)一返回undefined

還有一些邊邊角角的特性,比如:

  • 安全的 construction: new a?.()
  • 安全的 template literal:a?.`string`
  • 安全的賦值:a?.b = c
  • 自增惹盼,自減:a?.b++, --a?.b
  • 解構(gòu)賦值: { x: a?.b } = c, [ a?.b ] = c
  • for 循環(huán)中的臨時(shí)賦值:for (a?.b in c), for (a?.b of c)

這些問題對(duì)開發(fā)者的理解成本較大庸汗,有些會(huì)支持,有些不會(huì)支持手报,有些委員會(huì)甚至都不想討論了蚯舱。這也難過OC提案這么多年才剛剛突破stage2。

小結(jié)

以前我也覺得OC這么甜的語(yǔ)法糖掩蛤,應(yīng)該盡早推出枉昏,不該整天瞎逼逼些無(wú)聊的話題。但事實(shí)上語(yǔ)言設(shè)計(jì)者的思考比我等深遠(yuǎn)許多揍鸟。JS本身就是很好的反面教材兄裂,當(dāng)年語(yǔ)言設(shè)計(jì)過于沖忙,直接導(dǎo)致了許多語(yǔ)法級(jí)別的bug阳藻;之后積重難返晰奖,給web開發(fā)留下了無(wú)數(shù)的巨坑。一個(gè)新語(yǔ)法的特性不是三言兩語(yǔ)腥泥,或是拍拍腦袋就決定的匾南。我就沒有考慮過OC有這么多邊界問題,其實(shí)歸根結(jié)底還是自己碰到的具體案例太少蛔外,沒有思考過特定場(chǎng)合的語(yǔ)義特性蛆楞。有時(shí)候我們?cè)诼裨鼓承┱Z(yǔ)言多年止步不前時(shí)候溯乒,也可以思考一下其中的難點(diǎn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末臊岸,一起剝皮案震驚了整個(gè)濱河市橙数,隨后出現(xiàn)的幾起案子尊流,更是在濱河造成了極大的恐慌帅戒,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崖技,死亡現(xiàn)場(chǎng)離奇詭異逻住,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)迎献,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門瞎访,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吁恍,你說我怎么就攤上這事扒秸。” “怎么了冀瓦?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵伴奥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我翼闽,道長(zhǎng)拾徙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任感局,我火速辦了婚禮尼啡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘询微。我一直安慰自己崖瞭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布撑毛。 她就那樣靜靜地躺著读恃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪代态。 梳的紋絲不亂的頭發(fā)上寺惫,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音蹦疑,去河邊找鬼西雀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛歉摧,可吹牛的內(nèi)容都是我干的艇肴。 我是一名探鬼主播腔呜,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼再悼!你這毒婦竟也來了核畴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤冲九,失蹤者是張志新(化名)和其女友劉穎谤草,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體莺奸,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丑孩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灭贷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片温学。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖甚疟,靈堂內(nèi)的尸體忽然破棺而出仗岖,到底是詐尸還是另有隱情,我是刑警寧澤览妖,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布轧拄,位于F島的核電站,受9級(jí)特大地震影響黄痪,放射性物質(zhì)發(fā)生泄漏紧帕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一桅打、第九天 我趴在偏房一處隱蔽的房頂上張望是嗜。 院中可真熱鬧,春花似錦挺尾、人聲如沸鹅搪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)丽柿。三九已至,卻和暖如春魂挂,著一層夾襖步出監(jiān)牢的瞬間甫题,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工涂召, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坠非,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓果正,卻偏偏與公主長(zhǎng)得像炎码,于是被迫代替她去往敵國(guó)和親盟迟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些閱讀 2,034評(píng)論 0 2
  • 專業(yè)考題類型管理運(yùn)行工作負(fù)責(zé)人一般作業(yè)考題內(nèi)容選項(xiàng)A選項(xiàng)B選項(xiàng)C選項(xiàng)D選項(xiàng)E選項(xiàng)F正確答案 變電單選GYSZ本規(guī)程...
    小白兔去釣魚閱讀 9,002評(píng)論 0 13
  • 在C語(yǔ)言中,五種基本數(shù)據(jù)類型存儲(chǔ)空間長(zhǎng)度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來閱讀 3,345評(píng)論 0 2
  • var navigator = navigator || {};var window = window || {}...
    DF_Sky閱讀 1,252評(píng)論 0 0
  • 第一部分:快速入門JS學(xué)習(xí)中的一些注意點(diǎn):2018.8.16基礎(chǔ)知識(shí): 入門JavaScript是世界上最流行的腳...
    天山雪蓮_38324閱讀 544評(píng)論 0 1