您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“JS私有屬性的實(shí)現(xiàn)方式有哪些”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“JS私有屬性的實(shí)現(xiàn)方式有哪些”這篇文章吧。
class 是創(chuàng)建對(duì)象的模版,由一系列屬性和方法構(gòu)成,用于表示對(duì)同一概念的數(shù)據(jù)和操作。
有的屬性和方法是對(duì)外的,但也有的是只想內(nèi)部用的,也就是私有的,那怎么實(shí)現(xiàn)私有屬性和方法呢?
不知道大家會(huì)怎么實(shí)現(xiàn),我梳理了下,我大概用過 6 種方式,我們分別來看一下:
區(qū)分私有和公有最簡單的方式就是加個(gè)下劃線 _,從命名上來區(qū)分。
比如:
class Dong { constructor() { this._name = 'dong'; this._age = 20; this.friend = 'guang'; } hello() { return 'I\'m ' + this._name + ', ' + this._age + ' years old'; } } const dong = new Dong(); console.log(dong.hello());
這里的 Dong 就有私有屬性 _name、_age,公有屬性 friend。
但是這種方式只是一種命名規(guī)范,告訴開發(fā)者這個(gè)屬性、方法是私有的,不要調(diào)用,但終究不是強(qiáng)制的,如果別人要用也阻止不了。
不過這種方式用的還是挺多的,歷史比較悠久。
那怎么能基于這種規(guī)范實(shí)現(xiàn)真正的私有呢?這就要用到 Proxy 了:
Proxy 可以定義目標(biāo)對(duì)象的 get、set、Object.keys 的邏輯,可以在這一層做一下判斷,如果是下劃線 _ 開頭就不讓訪問,否則就可以訪問。
比如還是這個(gè) class:
class Dong { constructor() { this._name = 'dong'; this._age = 20; this.friend = 'guang'; } hello() { return 'I\'m ' + this._name + ', ' + this._age + ' years old'; } } const dong = new Dong();
我們不直接調(diào)用它的對(duì)象的屬性方法了,而是先用一層 Proxy 來約束下 get、set、getKeys 的行為:
const dong = new Dong(); const handler = { get(target, prop) { if (prop.startsWith('_')) { return; } return target[prop]; }, set(target, prop, value) { if (prop.startsWith('_')) { return; } target[prop] = value; }, ownKeys(target, prop) { return Object.keys(target).filter(key => !key.startsWith('_')) }, } const proxy = new Proxy(dong, handler)
我們通過 new Proxy 來給 dong 定義了 get、set、ownKeys 的 handler:
get: 如果以下劃線 _ 開頭就返回空,否則返回目標(biāo)對(duì)象的屬性值 target[prop]
set: 如果以下劃線 _ 開頭就直接返回,否則設(shè)置目標(biāo)對(duì)象的屬性值
ownKeys: 訪問 keys 時(shí),過濾掉目標(biāo)對(duì)象中下劃線開頭的屬性再返回
這樣就實(shí)現(xiàn)了下劃線開頭的屬性的私有化:
我們測(cè)試下:
const proxy = new Proxy(dong, handler) for (const key of Object.keys(proxy)) { console.log(key, proxy[key]) }
確實(shí),這里只打印了公有屬性的方法,而下劃線開頭的那兩個(gè)屬性沒有打印。
我們基于 _prop 這種命名規(guī)范實(shí)現(xiàn)了真正的私有屬性!
再調(diào)用下方法試試:
咋是 undefined 了?
因?yàn)?proxy.hello 方法的 this 也是指向 proxy 的,也會(huì)受限制,所以要再做下處理:
如果用的是方法,那就給它綁定 this 為目標(biāo)對(duì)象。
這樣 hello 方法就可以訪問到那些 _ 開頭的私有屬性了:
我們通過 Proxy 給下劃線的命名規(guī)范實(shí)現(xiàn)了真正的私有屬性,但是要定義一層 Proxy 比較麻煩,有沒有不定義 Prxoy 的方式呢?
確實(shí)有,比如 Symbol:
Symbol 是 es2015 添加的一個(gè) api,用于創(chuàng)建唯一的值?;谶@個(gè)唯一的特性,我們就可以實(shí)現(xiàn)私有屬性。
比如這樣:
const nameSymbol = Symbol('name'); const ageSymbol = Symbol('age'); class Dong { constructor() { this[nameSymbol] = 'dong'; this[ageSymbol] = 20; } hello() { return 'I\'m ' + this[nameSymbol] + ', ' + this[ageSymbol] + ' years old'; } } const dong = new Dong();
我們不再用 name 和 age 作為私有屬性名了,而是用 Symbol 生成唯一的值來作為名字。
這樣外面因?yàn)槟貌坏綄傩悦?,就沒法取到對(duì)應(yīng)的屬性值:
這種方式比 Proxy 的方式更簡單一些,也是用的很多的一種實(shí)現(xiàn)私有屬性的方式。
如果想暴露出去,可以定義個(gè) get 方法:
但是這種私有屬性是真的沒法訪問么?
不是的,有一個(gè) api 叫做 Object.getOwnPropertySymbols,可以取到對(duì)象的所有 Symbols 屬性,然后就可以拿到屬性值了:
所以說這種方式只是 Object.keys 取不到對(duì)應(yīng)的屬性而已,不如 Proxy 那種方式完善。
那不用 Proxy 的方式,還比有沒有 Symbol 更完善的呢?
那可以試試這種:
外面可以訪問到屬性和方法是因?yàn)槲覀儼阉鼟斓搅?this 上,那不掛到 this 上外面不就訪問不到了么?
比如用一個(gè) Map 保存私有屬性:
const privateFields = new Map(); class Dong { constructor() { privateFields.set('name', 'dong'); privateFields.set('age', 20); } hello() { return 'I\'m ' + privateFields.get('name') + ', ' + privateFields.get('name') + ' years old'; } }
我們測(cè)試下:
這樣貌似可以,但不知道大家有沒有發(fā)現(xiàn)其中的問題:
所有對(duì)象都用同一個(gè) Map,之間相互影響
對(duì)象銷毀了這個(gè) Map 依然存在
怎么解決這個(gè)問題呢?
不知道大家用沒用過 WeakMap,它的特性是只能用對(duì)象作為 key,對(duì)象銷毀,這個(gè)鍵值對(duì)就銷毀。
完美解決了上面兩個(gè)問題:
因?yàn)槭怯脤?duì)象作為 key 的,那不同的對(duì)象是放在不同的鍵值對(duì)上的,相互沒影響
對(duì)象銷毀的時(shí)候,對(duì)應(yīng)的鍵值對(duì)就銷毀,不需要手動(dòng)管理
貌似是很完美,我們實(shí)現(xiàn)下:
const dongName = new WeakMap(); const dongAge = new WeakMap(); const classPrivateFieldSet = function(receiver, state, value) { state.set(receiver, value); } const classPrivateFieldGet = function(receiver, state) { return state.get(receiver); } class Dong { constructor() { dongName.set(this, void 0); dongAge.set(this, void 0); classPrivateFieldSet(this, dongName, 'dong'); classPrivateFieldSet(this, dongAge, 20); } hello() { return 'I\'m ' + classPrivateFieldGet(this, dongName) + ', ' + classPrivateFieldGet(this, dongAge) + ' years old'; } }
每個(gè)屬性定義了一個(gè) WeakMap 來維護(hù),key 為當(dāng)前對(duì)象,值為屬性值,get 和 set 使用 classPrivateFieldSet 和 classPrivateFieldGet 這兩個(gè)方法,最終是通過從 WeakMap 中存取的。
在構(gòu)造器里初始化下當(dāng)前對(duì)象對(duì)應(yīng)的屬性值,也就是 dongName.set(this, void 0),這里的 void 0 的返回值是 undefined,一個(gè)意思。
測(cè)試下:
哇,通過 WeakMap 也能實(shí)現(xiàn)私有屬性!
不過這里的 classPrivateFieldGet 沒必要定義吧,直接 xxMap.get 不就行么?
確實(shí),包一層的目的是為了可以加一些額外的邏輯,這里也可以直接從 weakMap 取。
但這樣寫起來也很麻煩呀,有沒有更簡單的方式呢?
能不能設(shè)計(jì)一種語法糖,它自動(dòng)編譯成這種方式呢?
想的沒錯(cuò),確實(shí)有這種語法糖:
現(xiàn)在有一個(gè)私有屬性的 es 草案,可以通過 # 的方式來標(biāo)識(shí)私有屬性和方法。
比如這樣:
class Dong { constructor() { this.#name = 'dong'; this.#age = 20; this.friend = 'guang'; } hello() { return 'I\'m ' + this.#name + this.#age + 'years old'; } }
這里的 name 和 age 都是私有的,而 friend 是公有的。
這種新語法 JS 引擎沒那么快支持,但是可以通過 babel 或者 ts 編譯器來編譯成低版本語法的方式來提前用。
比如 babel 有 @babel/proposal-private-property-in-object 的插件,它可以實(shí)現(xiàn)這種語法的編譯:
babel 就是把 #prop 編譯成上面那種 WeakMap 的方式來實(shí)現(xiàn)的。
這個(gè)插件在 @babel/preset-env 的預(yù)設(shè)里,會(huì)自動(dòng)引入:
除了 babel,ts 里也可以直接用這種語法:
也是會(huì)編譯成 WeakMap 的方式來實(shí)現(xiàn)。
其實(shí) ts 實(shí)現(xiàn)的新語法還是不少的,比如 ? 和 ?? 分別是可選鏈和默認(rèn)值的語法,下面這兩種寫法等價(jià):
const res = data?.name ?? 'dong'; const res2 = data && data.name || 'dong';
這種新語法都是直接可用的,babel 的話需要引入下 proposal 插件。
對(duì)了,我記得 ts 里 class 也是有 private 的修飾符的,那個(gè)不也是私有屬性么?
其實(shí)它是私有屬性但也不完全是,我們來看一下:
ts 可以通過 private 來修飾屬性、方法的可見性:
private 表示屬性私有,只有 class 內(nèi)部可訪問
protected 表示保護(hù),只有 class 和子 class 可訪問
public 表示公有,外部也可訪問
類型檢查和提示的時(shí)候是有區(qū)別的,比如 private 屬性在 class 外部不可訪問:
而 class 內(nèi)部是可以訪問的:
但是這種約束只是用于類型檢查的,只存在編譯期間,運(yùn)行時(shí)并沒有這種約束。
我們可以看下編譯后的代碼:
可以看到?jīng)]有做任何處理。
而如果用 #prop 的方式,除了編譯時(shí)是 private 的,運(yùn)行時(shí)也是:
所以,要實(shí)現(xiàn)真正的 private 的話,還是用 #prop 的方式,如果只是編譯時(shí)約束那聲明下 private 就行。
class 用于定義圍繞某個(gè)概念的一系列屬性和方法,這些屬性和方法有的是內(nèi)部用的,有的是對(duì)外的。只有內(nèi)部用的屬性、方法需要實(shí)現(xiàn)私有化。
實(shí)現(xiàn)私有屬性方法,我樹立了 6 種方式:
通過下劃線 _prop 從命名上區(qū)分
通過 Proxy 來定義 get、set、ownKeys 的邏輯
通過 Symbol 來定義唯一的屬性名,不能通過 keys 拿到
通過 WeakMap 來保存所有對(duì)象的私有屬性和方法
通過 #prop 的 es 新語法實(shí)現(xiàn)私有,babel 和 tsc 會(huì)把它們編譯成 WeakMap 的方式
通過 ts 的 private 在編譯時(shí)約束
這六種方式,有三種只是偽私有,比如 _prop(依然可以訪問)、ts 的 private(運(yùn)行時(shí)可訪問)、Symbol(可以通過 Object.getOwnSymbols 拿到 symbol 來訪問)。
另外三種是真正的私有,包括 Proxy、WeakMap、#prop(目前是編譯為 WeakMap 的方式)。
有的是從屬性名上想辦法,比如 _prop 和 Symbol,有的是從 this 上想辦法,比如 Proxy(包一層) 和 WeakMap(不掛到 this),有的是從語言本身想辦法,比如 ts 的 private 或者 es 新語法的 #prop。
以上是“JS私有屬性的實(shí)現(xiàn)方式有哪些”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。