大家都知道 JavaScript 的對象屬性默認是可以被從外部訪問和修改的,也就是說蜈首,JavaScript 本身不存在完全“私有”的對象屬性实抡。例如:
在上面的代碼里,我們約定俗成地用下劃線開頭來表示私有變量欢策。我們希望 _x吆寨、_y 不被外部訪問,然而踩寇,這只是我們一廂情愿啄清,使用者還是可以訪問到這兩個變量。
在這里俺孙,我們不討論 ES 的 private 標準提案辣卒,而是討論如何使用工具來將約定變成真正的私有。
使用 Symbol 來構(gòu)造私有數(shù)據(jù)
ES6 提供了一個新的數(shù)據(jù)類型叫做 Symbol睛榄,Symbol 有許多用途荣茫,其中一個用途是可以用來生成唯一 key,用作屬性標識场靴,我們利用它可以實現(xiàn)真正的私有屬性:
我們改寫上一版的代碼啡莉,用 Symbol 的 _x、_y 代替字符串來作為 key旨剥,這樣咧欣,外部 p 訪問 _x、_y 屬性就訪問不到了轨帜,這樣我們就真正實現(xiàn)了對象數(shù)據(jù)的私有魄咕。
上面這種用法并不復(fù)雜,但是蚌父,如果我們每次定義對象都這么去寫還是顯得麻煩哮兰。因此,我們可以考慮讓編譯器去做這件事情苟弛,自動將下劃線開頭的屬性編譯成私有屬性喝滞。
使用 Babel 插件來實現(xiàn)屬性的默認私有
在這里,我們可以開發(fā) Babel 的插件來實現(xiàn)嗡午。Babel 的原理在博客之前的文章中有介紹囤躁。還有使用 Babel 插件來進行測試覆蓋度檢查的例子冀痕。如果對于 Babel 不熟悉的同學(xué)荔睹,可以回顧一下之前的文章狸演。
首先,我們分析一下要處理的 AST 部分僻他。ES6 的 class 有兩種 node 類型宵距,一種是 ClassDeclaration,另一種是 ClassExpression吨拗。它們比較類似满哪,但是在一些細節(jié)上又有區(qū)別。比如 ReturnStatement 之后可以跟 ClassExpression 但是不能跟 ClassDeclaration劝篷。
ClassDeclaration 與 ClassExpression
對這兩種 node哨鸭,如果其中有下劃線開頭的屬性,可以分別編譯成如下形式:
此外娇妓,還需要考慮 ES Modules 的情況:
對應(yīng)為:
上面的形式?jīng)]有問題像鸡。但是如果:
對應(yīng)為:
編譯會報錯。因此要進行修改哈恰,對應(yīng)成:
由于 Class 允許存在嵌套只估,因此,我們需要使用堆棧結(jié)構(gòu)着绷,在 AST 的 enter 的時候創(chuàng)建存放當前 Class 的 scope 下的私有屬性列表蛔钙。堆棧還有一個作用,就是如果堆棧為空荠医,那么當前作用域不在 Class 內(nèi)部吁脱,不用進行編譯轉(zhuǎn)換。
接下來子漩,我們處理具體的 Identifier:
Protected 的屬性和 super._x 操作
對于對象方法帶下劃線的情況豫喧,和 this 帶下劃線不同,我們是可以使用 super.屬性名 來訪問的幢泼。比如:
在這里紧显,我們需要對 super._X 進行處理,如果直接編譯:
由于每個 Symbol 都是唯一的缕棵,所以 Bar 的 Symbol('X_XX') 和 Foo 的并不相同孵班,這樣也就獲取不到 super[X_XX?] 實際的值了。
因此招驴,在這里篙程,我們編譯的時候不能直接這樣轉(zhuǎn)成 Symbol,而是要通過反射機制去處理:
上面的 super 里的 key 有一大串别厘,是:
這里通過 Object.getOwnPropertySymbols(this.__proto__.__proto__) 反射出父類的 Symbol虱饿,然后通過字符串匹配到對應(yīng)的 key。
于是,我們確定了轉(zhuǎn)換方法氮发,那么接下來就只是實現(xiàn)具體的轉(zhuǎn)換細節(jié)了:
上面的方法將 ClassDeclaration 和 ClassExpression 處理完成渴肉。接下來是處理 super 屬性的部分:
上面代碼雖然繁瑣,但都并不復(fù)雜爽冕,只是 AST 樹的構(gòu)建而已仇祭。最終,我們形成完整的插件代碼颈畸。有興趣的同學(xué)可以關(guān)注這個 GitHub repo乌奇。
要使用的話,直接安裝:
然后配置一下:
其中配置的 pattern 參數(shù)可以修改私有變量的匹配正則表達式眯娱,默認是 `"^_" 也就是以下劃線開頭礁苗,可以改成別的模式。
結(jié)尾 需要這些資料徙缴,可以關(guān)注公眾號 清風(fēng)酔 領(lǐng)取