<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
class 是建立物件的模版,由一系列屬性和方法構成,用於表示對同一概念的資料和操作。
有的屬性和方法是對外的,但也有的是隻想內部用的,也就是私有的,那怎麼實現私有屬性和方法呢?
不知道大家會怎麼實現,我梳理了下,我大概用過 6 種方式,我們分別來看一下:
區分私有和公有最簡單的方式就是加個下劃線 _,從命名上來區分。
比如:
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。
但是這種方式只是一種命名規範,告訴開發者這個屬性、方法是私有的,不要呼叫,但終究不是強制的,如果別人要用也阻止不了。
不過這種方式用的還是挺多的,歷史比較悠久。
那怎麼能基於這種規範實現真正的私有呢?這就要用到 Proxy 了:
Proxy 可以定義目標物件的 get、set、Object.keys 的邏輯,可以在這一層做一下判斷,如果是下劃線 _ 開頭就不讓存取,否則就可以存取。
比如還是這個 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();
我們不直接呼叫它的物件的屬性方法了,而是先用一層 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:
這樣就實現了下劃線開頭的屬性的私有化:
我們測試下:
const proxy = new Proxy(dong, handler) for (const key of Object.keys(proxy)) { console.log(key, proxy[key]) }
確實,這裡只列印了公有屬性的方法,而下劃線開頭的那兩個屬性沒有列印。
我們基於 _prop 這種命名規範實現了真正的私有屬性!
再呼叫下方法試試:
咋是 undefined 了?
因為 proxy.hello 方法的 this 也是指向 proxy 的,也會受限制,所以要再做下處理:
如果用的是方法,那就給它繫結 this 為目標物件。
這樣 hello 方法就可以存取到那些 _ 開頭的私有屬性了:
我們通過 Proxy 給下劃線的命名規範實現了真正的私有屬性,但是要定義一層 Proxy 比較麻煩,有沒有不定義 Prxoy 的方式呢?
確實有,比如 Symbol:
Symbol 是 es2015 新增的一個 api,用於建立唯一的值。基於這個唯一的特性,我們就可以實現私有屬性。
比如這樣:
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 生成唯一的值來作為名字。
這樣外面因為拿不到屬性名,就沒法取到對應的屬性值:
這種方式比 Proxy 的方式更簡單一些,也是用的很多的一種實現私有屬性的方式。
如果想暴露出去,可以定義個 get 方法:
但是這種私有屬性是真的沒法存取麼?
不是的,有一個 api 叫做 Object.getOwnPropertySymbols,可以取到物件的所有 Symbols 屬性,然後就可以拿到屬性值了:
所以說這種方式只是 Object.keys 取不到對應的屬性而已,不如 Proxy 那種方式完善。
那不用 Proxy 的方式,還比有沒有 Symbol 更完善的呢?
那可以試試這種:
外面可以存取到屬性和方法是因為我們把它掛到了 this 上,那不掛到 this 上外面不就存取不到了麼?
比如用一個 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'; } }
我們測試下:
這樣貌似可以,但不知道大家有沒有發現其中的問題:
怎麼解決這個問題呢?
不知道大家用沒用過 WeakMap,它的特性是隻能用物件作為 key,物件銷燬,這個鍵值對就銷燬。
完美解決了上面兩個問題:
貌似是很完美,我們實現下:
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'; } }
每個屬性定義了一個 WeakMap 來維護,key 為當前物件,值為屬性值,get 和 set 使用 classPrivateFieldSet 和 classPrivateFieldGet 這兩個方法,最終是通過從 WeakMap 中存取的。
在構造器裡初始化下當前物件對應的屬性值,也就是 dongName.set(this, void 0),這裡的 void 0 的返回值是 undefined,一個意思。
測試下:
哇,通過 WeakMap 也能實現私有屬性!
不過這裡的 classPrivateFieldGet 沒必要定義吧,直接 xxMap.get 不就行麼?
確實,包一層的目的是為了可以加一些額外的邏輯,這裡也可以直接從 weakMap 取。
但這樣寫起來也很麻煩呀,有沒有更簡單的方式呢?
能不能設計一種語法糖,它自動編譯成這種方式呢?
想的沒錯,確實有這種語法糖:
現在有一個私有屬性的 es 草案,可以通過 # 的方式來標識私有屬性和方法。
比如這樣:
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 的外掛,它可以實現這種語法的編譯:
babel 就是把 #prop 編譯成上面那種 WeakMap 的方式來實現的。
這個外掛在 @babel/preset-env 的預設裡,會自動引入:
除了 babel,ts 裡也可以直接用這種語法:
也是會編譯成 WeakMap 的方式來實現。
其實 ts 實現的新語法還是不少的,比如 ? 和 ?? 分別是可選鏈和預設值的語法,下面這兩種寫法等價:
const res = data?.name ?? 'dong'; const res2 = data && data.name || 'dong';
這種新語法都是直接可用的,babel 的話需要引入下 proposal 外掛。
對了,我記得 ts 裡 class 也是有 private 的修飾符的,那個不也是私有屬性麼?
其實它是私有屬性但也不完全是,我們來看一下:
ts 可以通過 private 來修飾屬性、方法的可見性:
型別檢查和提示的時候是有區別的,比如 private 屬性在 class 外部不可存取:
而 class 內部是可以存取的:
但是這種約束只是用於型別檢查的,只存在編譯期間,執行時並沒有這種約束。
我們可以看下編譯後的程式碼:
可以看到沒有做任何處理。
而如果用 #prop 的方式,除了編譯時是 private 的,執行時也是:
所以,要實現真正的 private 的話,還是用 #prop 的方式,如果只是編譯時約束那宣告下 private 就行。
class 用於定義圍繞某個概念的一系列屬性和方法,這些屬性和方法有的是內部用的,有的是對外的。只有內部用的屬性、方法需要實現私有化。
實現私有屬性方法,我樹立了 6 種方式:
這六種方式,有三種只是偽私有,比如 _prop(依然可以存取)、ts 的 private(執行時可存取)、Symbol(可以通過 Object.getOwnSymbols 拿到 symbol 來存取)。
另外三種是真正的私有,包括 Proxy、WeakMap、#prop(目前是編譯為 WeakMap 的方式)。
有的是從屬性名上想辦法,比如 _prop 和 Symbol,有的是從 this 上想辦法,比如 Proxy(包一層) 和 WeakMap(不掛到 this),有的是從語言本身想辦法,比如 ts 的 private 或者 es 新語法的 #prop。
這 6 種實現私有屬性的方式,你用過幾種?
以上就是一文詳解JS私有屬性的6種實現方式的詳細內容,更多關於JS私有屬性的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45