天天看點

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           

優點

  1. 寫法簡單
  2. 調試友善
  3. 相容性好

缺點

  1. 外部可以通路和修改
  2. 語言沒有配合的機制,如 for in 語句會将所有屬性枚舉出來
  3. 命名沖突

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           

  1. 無命名沖突
  2. 外部無法通路和修改

  1. constructor 的邏輯變得複雜。構造函數應該隻做對象初始化的事情,現在為了實作私有變量,必須包含部分方法的實作,代碼組織上略不清晰。
  2. 方法存在于執行個體,而非原型上,子類也無法使用 super 調用
  3. 建構增加一點點開銷

實作二

/**
 * 實作二
 */
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           

  1. 寫法有一點複雜

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           

  1. 無性能損失

  1. 寫法稍微複雜
  2. 相容性也還好

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           

  1. 寫法比較麻煩
  2. 相容性有點問題
  3. 有一定性能代價

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 的執行個體,然後再接着擷取值。

這也意味着每次屬性通路都需要做這樣一個判斷,而引擎已經圍繞屬性通路做了高度優化,懶得改,而且還降低速度。

不過除了這個工作之外,還會有一些其他的内容需要考慮,比如說:

  1. 你必須将私有的 key 編碼進每個詞法環境
  2. for in 可以周遊這些屬性嗎?
  3. 私有屬性和正常屬性同名的時候,誰會屏蔽誰?
  4. 怎麼防止私有屬性的名稱不被探測出來。

關于使用 # 而不使用 private 更多的讨論可以參考這個

Issue

當然這些問題都可以被解決啦,就是麻煩了點。

而如果你選擇 #,實作的方式将跟 JavaScript 對象屬性完全沒有關系,将會使用

private slots

的方式以及使用一個新的 slot 查找文法,總之就是會比 private 的實作方式簡單很多。

參考

  1. 《程式設計語言如何演化——以JS的private為例》賀師俊
  2. Exploring ES6
  3. 譯 JS 新文法:私有屬性

ES6 系列

ES6 系列目錄位址:

https://github.com/mqyqingfeng/Blog

ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的了解,重點講解塊級作用域、标簽模闆、箭頭函數、Symbol、Set、Map 以及 Promise 的模拟實作、子產品加載方案、異步處理等内容。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。

繼續閱讀