ES6系列之私有變數的實現
前言
在閱讀 《ECMAScript 6 入門》的時候,零散的看到有私有變數的實現,所以在此總結一篇。
1. 約定
實現
class Example {
constructor() {
this._private = `private`;
}
getName() {
return this._private
}
}
var ex = new Example();
console.log(ex.getName()); // private
console.log(ex._private); // private
優點
- 寫法簡單
- 除錯方便
- 相容性好
缺點
- 外部可以訪問和修改
- 語言沒有配合的機制,如 for in 語句會將所有屬性列舉出來
- 命名衝突
2. 閉包
實現一
/**
* 實現一
*/
class Example {
constructor() {
var _private = ``;
_private = `private`;
this.getName = function() {return _private}
}
}
var ex = new Example();
console.log(ex.getName()); // private
console.log(ex._private); // undefined
優點
- 無命名衝突
- 外部無法訪問和修改
缺點
- constructor 的邏輯變得複雜。建構函式應該只做物件初始化的事情,現在為了實現私有變數,必須包含部分方法的實現,程式碼組織上略不清晰。
- 方法存在於例項,而非原型上,子類也無法使用 super 呼叫
- 構建增加一點點開銷
實現二
/**
* 實現二
*/
const Example = (function() {
var _private = ``;
class Example {
constructor() {
_private = `private`;
}
getName() {
return _private;
}
}
return Example;
})();
var ex = new Example();
console.log(ex.getName()); // private
console.log(ex._private); // undefined
優點
- 無命名衝突
- 外部無法訪問和修改
缺點
- 寫法有一點複雜
- 構建增加一點點開銷
3. Symbol
實現
const Example = (function() {
var _private = Symbol(`private`);
class Example {
constructor() {
this[_private] = `private`;
}
getName() {
return this[_private];
}
}
return Example;
})();
var ex = new Example();
console.log(ex.getName()); // private
console.log(ex.name); // undefined
優點
- 無命名衝突
- 外部無法訪問和修改
- 無效能損失
缺點
- 寫法稍微複雜
- 相容性也還好
4. WeakMap
實現
/**
* 實現一
*/
const _private = new WeakMap();
class Example {
constructor() {
_private.set(this, `private`);
}
getName() {
return _private.get(this);
}
}
var ex = new Example();
console.log(ex.getName()); // private
console.log(ex.name); // undefined
如果這樣寫,你可能覺得封裝性不夠,你也可以這樣寫:
/**
* 實現二
*/
const Example = (function() {
var _private = new WeakMap(); // 私有成員儲存容器
class Example {
constructor() {
_private.set(this, `private`);
}
getName() {
return _private.get(this);
}
}
return Example;
})();
var ex = new Example();
console.log(ex.getName()); // private
console.log(ex.name); // undefined
優點
- 無命名衝突
- 外部無法訪問和修改
缺點
- 寫法比較麻煩
- 相容性有點問題
- 有一定效能代價
5. 最新提案
class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
}
equals(point) {
return this.#x === point.#x && this.#y === point.#y;
}
}
那麼為什麼不直接使用 private 欄位呢?比如說這樣:
class Foo {
private value;
equals(foo) {
return this.value === foo.value;
}
}
簡單點來說,就是嫌麻煩,當然也有效能上的考慮……
舉個例子,如果我們不使用 #,而是使用 private 關鍵字:
class Foo {
private value = `1`;
equals(foo) {
return this.value === foo.value;
}
}
var foo1 = new Foo();
var foo2 = new Foo();
console.log(foo1.equals(foo2));
在這裡我們新建了兩個例項,然後將 foo2 作為引數傳入了 foo1 的例項方法中。
那麼我們可以獲取 foo2.value 的值嗎?如果我們直接 foo2.value
肯定是獲取不到值的,畢竟是私有變數,可是 equals 是 Foo 的一個類方法,那麼可以獲取到的嗎?
答案是可以的。
其實這點在其他語言,比如說 Java 和 C++ 中也是一樣的,類的成員函式中可以訪問同型別例項的私有變數,這是因為私有是為了實現“對外”的資訊隱藏,在類自己內部,沒有必要禁止私有變數的訪問,你也可以理解為私有變數的限制是以類為單位,而不是以物件為單位,此外這樣做也可以為使用者帶來便利。
既然獲取值是可以的,那麼列印的結果應該為 true,但是如果我們傳入的值不是 Foo 的例項,而是一個其他物件呢?
var foo1 = new Foo();
console.log(foo1.equals({
value: 2
}));
當然這裡程式碼也是可以正常執行的,但是對於編譯器來說,就有一點麻煩了,因為編譯器不知道 value 到底是 foo 的正常屬性還是私有屬性,所以編譯器需要做判斷,先判斷 foo 是不是 Foo 的例項,然後再接著獲取值。
這也意味著每次屬性訪問都需要做這樣一個判斷,而引擎已經圍繞屬性訪問做了高度優化,懶得改,而且還降低速度。
不過除了這個工作之外,還會有一些其他的內容需要考慮,比如說:
- 你必須將私有的 key 編碼進每個詞法環境
- for in 可以遍歷這些屬性嗎?
- 私有屬性和正常屬性同名的時候,誰會遮蔽誰?
- 怎麼防止私有屬性的名稱不被探測出來。
關於使用 # 而不使用 private 更多的討論可以參考這個 Issue。
當然這些問題都可以被解決啦,就是麻煩了點。
而如果你選擇 #,實現的方式將跟 JavaScript 物件屬性完全沒有關係,將會使用 private slots
的方式以及使用一個新的 slot 查詢語法,總之就是會比 private 的實現方式簡單很多。
參考
ES6 系列
ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標籤模板、箭頭函式、Symbol、Set、Map 以及 Promise 的模擬實現、模組載入方案、非同步處理等內容。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
相關文章
- ES6 系列之私有變數的實現變數
- ES6 系列之模擬實現 Symbol 型別Symbol型別
- ES6之變數的解構賦值變數賦值
- [譯] JavaScript 中的私有變數JavaScript變數
- Python中私有變數和私有方法Python變數
- dart系列之:dart語言中的變數Dart變數
- ES6入門之變數的解構賦值變數賦值
- 淺談 class 私有變數變數
- 淺談class私有變數變數
- es6 ${}’變數變數
- ES6 系列之模擬實現一個 Set 資料結構資料結構
- ES6 系列之 WeakMap
- ES6系列之非同步處理實戰非同步
- ES6 系列之非同步處理實戰非同步
- ES6標準入門之變數的解構賦值變數賦值
- ES6系列入門學習記錄:變數的解構賦值變數賦值
- JavaScript中是如何定義私有變數的JavaScript變數
- javascript:私有變數 (靜態私有變數為什麼會被所以例項共享?-答疑解惑)JavaScript變數
- ES6 系列之模板字串字串
- 筆記-go反射操作私有變數筆記Go反射變數
- ES6變數解構變數
- ES6:變數的結構賦值變數賦值
- ES6 系列之 defineProperty 與 proxy
- ES6系列之 let 和 const
- ES6 系列之迭代器與 for of
- 淺談php變數的實現-PHPPHP變數
- ES6 實現之介面卡模式 Adapter模式APT
- ES6 系列之 Generator 的自動執行
- ES6 - 變數的解構賦值解析變數賦值
- 深入理解Go系列一之指標變數Go指標變數
- ES6 -- 變數的解構賦值的用途變數賦值
- 在python中什麼是私有變數域Python變數
- ES6 系列之箭頭函式函式
- 通俗易懂理解ES6 - ES6的變數型別及Iterator變數型別
- ES6 變數作用域總結變數
- ES6系列之專案中常用的新特性
- 深入ES6 三 變數的解構賦值變數賦值
- [譯] ES6:理解引數預設值的實現細節