JavaScript實(shí)現(xiàn)私有屬性的幾種方式小結(jié)
什么是私有屬性
我們不摳定義,用大白話(huà)來(lái)說(shuō),如果類(lèi)中的某個(gè)屬性只能在類(lèi)內(nèi)部使用,在類(lèi)外部(比如通過(guò)類(lèi)的實(shí)例)訪(fǎng)問(wèn)不到,這個(gè)屬性就是私有屬性。
我們用下面的代碼舉例說(shuō)明,當(dāng)然了,代碼中的屬性并不是私有屬性,只是為了說(shuō)明私有屬性是怎么一回事:
class Person { name = 'name' // 我們假設(shè)name是一個(gè)私有屬性,當(dāng)然,它現(xiàn)在不是 getName() { return this.name // 類(lèi)內(nèi)部可以訪(fǎng)問(wèn)私有屬性 } } const person = new Person() person.getName() // 'name' person.name // 嘗試直接在類(lèi)外部訪(fǎng)問(wèn)私有屬性會(huì)報(bào)錯(cuò)
如何實(shí)現(xiàn)
早期JavaScript并不支持私有屬性,所以只能通過(guò)一些變通的方法曲線(xiàn)救國(guó)。
基于命名規(guī)范的的弱約束
一種方式是在命名規(guī)范上加以約束,約定下劃線(xiàn)開(kāi)頭的屬性是私有屬性。
class Person { _name = 'name' // 約定下劃線(xiàn)開(kāi)頭的屬性是私有屬性 getName() { return this._name // 類(lèi)內(nèi)部可以訪(fǎng)問(wèn)私有屬性 } } const person = new Person() person._name // 開(kāi)發(fā)者可以不遵守命名規(guī)范,運(yùn)行時(shí)在類(lèi)外部訪(fǎng)問(wèn)完全沒(méi)問(wèn)題
vue源碼中也有很多地方用下劃線(xiàn)開(kāi)頭的命名來(lái)表示屬性和變量。
但這終究是一種弱約束,運(yùn)行時(shí)完全可以在類(lèi)外部訪(fǎng)問(wèn)到這些屬性,沒(méi)有任何問(wèn)題。
基于閉包
function Person(){ const name = 'name' this.getName = function(){ return name } } const person = new Person() person.getName() // 'name' person.name // undefined
上面的代碼getName
函數(shù)引用了Person
函數(shù)的詞法環(huán)境,利用閉包的特性實(shí)現(xiàn)了私有屬性。私有屬性name
在Person
外部無(wú)法訪(fǎng)問(wèn),只能通過(guò)特權(quán)方法getName
訪(fǎng)問(wèn)到。
不過(guò)這種方式的缺點(diǎn)也很明顯:
私有屬性和特權(quán)方法都只能在構(gòu)造函數(shù)內(nèi)部聲明,而且,這里方法并不是掛載在原型上的,每實(shí)例化一個(gè)對(duì)象,就會(huì)生成一次方法。
將私有屬性移動(dòng)到類(lèi)外部結(jié)合ES模塊
const name = 'name' export class Person { getName() { return name } }
上面的代碼,ES模塊僅導(dǎo)出類(lèi),不導(dǎo)出類(lèi)外部的變量name
,這樣一來(lái)類(lèi)可以訪(fǎng)問(wèn)到變量name
,而外部則訪(fǎng)問(wèn)不到。
const person = new Person() person.getName() // 'name' person.name // undefined
基于Symbol
const name = Symbol('name') export class Person { [name] = 'name' getName() { return this[name] } }
上面的代碼用變量存儲(chǔ)了一個(gè)Symbol值,在類(lèi)內(nèi)部通過(guò)動(dòng)態(tài)屬性的方式為類(lèi)添加了一個(gè)私有屬性。同樣的基于ES模塊僅導(dǎo)出類(lèi),而不導(dǎo)出Symbol。這樣在使用的時(shí)候就無(wú)法訪(fǎng)問(wèn)Symbol值聲明的私有屬性了。
const person = new Person() person.getName() // 'name'
但是,其實(shí)還是有辦法獲取到這個(gè)Symbol值的。
const symbols = Object.getOwnPropertySymbols(person) person[symbols[0])
所以,這種方式也并沒(méi)有那么私有。
TypeScript中的private
TypeScript中不是有private
修飾符嗎,用這個(gè)試試怎么樣呢?
class Person { private name = 'name' getName() { return name } } const person = new Person() person.getName() // 'name' person.name // 編譯時(shí)錯(cuò)誤 Property 'name' is private and only accessible within class 'Person'.
在TypeScript中試圖在類(lèi)外部訪(fǎng)問(wèn)private
屬性會(huì)在編譯時(shí)報(bào)錯(cuò),看起來(lái)很美好對(duì)吧。但別忘了TypeScript終究要被編譯成JavaScript的,我們來(lái)看看編譯結(jié)果:
編譯成JavaScript后,private
修飾符沒(méi)有了。如果我們通過(guò)動(dòng)態(tài)屬性繞過(guò)編譯時(shí)的類(lèi)型檢查,編譯后的JavaScript代碼在運(yùn)行時(shí)并不會(huì)報(bào)錯(cuò):
person['name'] // 'name'
ES2022
ES2022正式引入了私有屬性,在屬性名前加上#
來(lái)表示私有屬性。
class Person { #name = 'name' getName() { return this.#name } } const person = new Person() person.getName() // 'name' person.#name // Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class
不過(guò)如果你把上面的代碼放在Chrome控制臺(tái)中執(zhí)行,可能會(huì)發(fā)現(xiàn)person.#name
是可以訪(fǎng)問(wèn)到值的。這是因?yàn)閺腃hrome111開(kāi)始,開(kāi)發(fā)者工具里面可以讀寫(xiě)私有屬性,不會(huì)報(bào)錯(cuò),原因是 Chrome 團(tuán)隊(duì)認(rèn)為這樣方便調(diào)試。
查看MDN了解更多有關(guān)私有屬性的知識(shí)。
WeakMap解決目前的兼容性
如果要考慮ES2022之前的兼容性,還可以用WeakMap來(lái)實(shí)現(xiàn)。
const privateFields = new WeakMap() export class Person { constructor() { privateFields.set(this, { name: 'name' }) } getName() { return privateFields.get(this).name } }
上面的代碼在類(lèi)外部維護(hù)了一個(gè)weakMap,然后在constructor中向weakMap綁定了實(shí)例this
和{name: 'name'}
的映射關(guān)系。訪(fǎng)問(wèn)的時(shí)候同樣通過(guò)this
從weakMap中取出name
。
同樣得益于ES模塊的特性,在模塊外部訪(fǎng)問(wèn)不到weakMap,自然就無(wú)法訪(fǎng)問(wèn)到私有屬性了。
const person = new Person() person.getName() // 'name'
不過(guò)這樣的寫(xiě)法也有缺點(diǎn),就是寫(xiě)法太繁瑣了,不夠直觀(guān)。其實(shí)上面ES2022私有屬性方案在babel編譯后的代碼基本就是和現(xiàn)在類(lèi)似的方案。
可以看到babel編譯后的代碼變多了很多,因?yàn)橐WC程序的健壯性,必須考慮很多邊緣場(chǎng)景。僅看我紅框框出的代碼也可以看出,編譯后的代碼確實(shí)是采用了WeakMap的方案。
總結(jié)
本文總結(jié)了JavaScript中實(shí)現(xiàn)私有屬性的幾種方式,ES2022引入的私有屬性正式寫(xiě)法自然是正規(guī)軍,而且寫(xiě)法也很簡(jiǎn)潔。如果要考慮兼容性,WeakMap方案確實(shí)保證了私有性,不過(guò)寫(xiě)法略繁瑣。其余方案或多或少不夠健壯,了解即可。
以上就是JavaScript實(shí)現(xiàn)私有屬性的幾種方式小結(jié)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript私有屬性的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS事件Event元素(兼容IE,Firefox,Chorme)
今天,想聊聊JS事件對(duì)象。相信大家對(duì)于獲得激發(fā)JS事件的原對(duì)象的理解,有的人可能簡(jiǎn)單停留在IE上。也就是window.event這個(gè)對(duì)象2012-11-11原生微信小程序中封裝一個(gè)模擬select下拉框組件代碼示例
這篇文章主要給大家介紹了關(guān)于原生微信小程序中封裝一個(gè)模擬select下拉框組件的相關(guān)資料,文中介紹了如何在小程序中創(chuàng)建和使用自定義組件van-select,包括組件的創(chuàng)建步驟和在頁(yè)面中的應(yīng)用方法,需要的朋友可以參考下2024-11-11生產(chǎn)制造追溯系統(tǒng)之再說(shuō)條碼打印
這篇文章主要介紹了生產(chǎn)制造追溯系統(tǒng)之再說(shuō)條碼打印,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-06-06JavaScript獲取flash對(duì)象與網(wǎng)上的有所不同
關(guān)于js獲取flash對(duì)象,網(wǎng)上有非常多的例子,但不是我想要的,經(jīng)測(cè)試整理,因此本文誕生了2014-04-04javascript高級(jí)編程之函數(shù)表達(dá)式 遞歸和閉包函數(shù)
這篇文章主要介紹了javascript高級(jí)編程之函數(shù)表達(dá)式 遞歸和閉包函數(shù)的相關(guān)資料,需要的朋友可以參考下2015-11-11PHP自動(dòng)加載autoload和命名空間的應(yīng)用小結(jié)
PHP的自動(dòng)加載就是我們加載實(shí)例化類(lèi)的時(shí)候,不需要手動(dòng)去寫(xiě)require來(lái)導(dǎo)入這個(gè)class.php文件,程序自動(dòng)幫我們加載導(dǎo)入進(jìn)來(lái)這.篇文章主要介紹了PHP自動(dòng)加載autoload和命名空的應(yīng)用,需要的朋友可以參考下2017-12-12TypeScript類(lèi)型斷言VS類(lèi)型守衛(wèi)示例詳解
這篇文章主要為大家介紹了TypeScript類(lèi)型斷言VS類(lèi)型守衛(wèi)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11