和不少高級語言不一樣,JavaScript 中沒有 public
、private
、protected
這些訪問修飾符(access modifiers),並且長期以來也沒有私有屬性這個概念,對象的屬性/方法默認都是public的。雖然目前 class 的私有屬性特性已經進入了 Stage3 實驗階段(Spec),經過 Babel 已經可使用,而且 Node v12 中也增長了對私有屬性的支持,但這並不妨礙咱們用 JS 的現有功能實現一個私有屬性特性,以加深對這一律唸的理解。git
私有屬性(方法)的意義在於將模塊的內部實現隱藏起來,而對外接口只經過public成員進行暴露,以減小其餘模塊對該模塊內部實現的依賴或修改,下降模塊的維護成本。
IIFE(當即執行函數) 你們應該耳熟能詳了,IIFE 常常被用來:github
基於以上特性,用 IIFE 能夠給一個對象實現簡單的私有屬性:babel
let person = (function () { // 私有屬性 let _name = "bruce"; return { age: 30, // getter get name() { return _name; }, // setter set name(val) { _name = val; }, greet: function () { console.log(`hi, i'm ${_name} and i'm ${this.age} years old`); } }; })();
測試一下:函數
console.log(person.name); // 'bruce' console.log(person._name); // undefined person.name = "frank"; console.log(person.name); // 'frank' console.log(Object.keys(person)); // ['age', 'name'] person.greet(); // hi, i'm frank and i'm 30 years old
IIFE 的實現簡單易懂,可是隻能做用於單個對象,而不能給 Class 或者構造函數定義私有屬性。測試
利用在構造函數中建立的局部變量能夠做爲 「私有屬性」 使用:this
function Person(name, age) { // 私有屬性 let _name = name; this.age = age; this.setName = function (name) { _name = name; }; this.getName = function () { return _name; }; } Person.prototype.greet = function (){ console.log(`hi, i'm ${this.getName()} and i'm ${this.age} years old`); }
測試一下:spa
const person = new Person("bruce", 30); console.log(person.getName()); // bruce person.setName('frank'); console.log(person.getName()); // frank person.greet(); // hi, i'm frank and i'm 30 years old
看起來還行,可是該實現方式須要在構造函數中定義 getter
、setter
方法,這兩個方法是綁定在實例上而不是原型上的,若是私有屬性增長會致使實例方法暴增,對內存不太友好。prototype
Class中實現和構造函數相似,由於JavaScript中的class本質上是構造函數和原型的語法糖,實現以下:code
class Person { constructor(name, age) { // 私有屬性 let _name = name; this.age = age; this.setName = function (name) { _name = name; }; this.getName = function () { return _name; }; } greet() { console.log(`hi, i'm ${this.getName()} and i'm ${this.age} years old`); } }
Class中的實現也會存在和構造函數中同樣的問題,並且在 greet()
方法中沒法訪問 _name
,須要經過調用 getter
方法。這和通常意義上的私有屬性仍是有差異的,真正的私有屬性在class內部應該是能夠正常訪問的,而不只僅是在構造函數內部能夠訪問。對象
以上三種實現或多或少都有一些問題,還好在ES2019中已經增長了對 class 私有屬性的原生支持,只須要在屬性/方法名前面加上 '#'
就能夠將其定義爲私有,而且支持定義私有的 static
屬性/方法。例如:
class Person { // 私有屬性 #name; constructor(name, age) { this.#name = name; this.age = age; } greet() { console.log(`hi, i'm ${this.#name} and i'm ${this.age} years old`); } }
測試一下:
const person = new Person("bruce", 30); console.log(person.name); // undefine person.greet(); // hi, i'm bruce and i'm 30 years old
更多語法能夠參考 MDN: Private class field。
咱們能夠去babel裏面將原生的代碼轉換一下,看看babel的polyfill是怎麼實現的:
發現主要思路竟然使用 WeakMap
。。好吧,仍是太年輕。格式化後的polyfill代碼貼在下面,有興趣的同窗能夠研究一下:
"use strict"; function _classPrivateFieldGet(receiver, privateMap) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to get private field on non-instance"); } if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to set private field on non-instance"); } if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } return value; } var _name = new WeakMap(); class Person { constructor(name, age) { _name.set(this, { writable: true, value: void 0, }); _classPrivateFieldSet(this, _name, name); this.age = age; } greet() { console.log( "hi, i'm " .concat(_classPrivateFieldGet(this, _name), " and i'm ") .concat(this.age, " years old") ); } }
天天一個小技巧(Tricks by Day),量變引發質變,但願你和我一塊兒天天多學一點,讓技術有趣一點。全部示例將會彙總到個人 tricks-by-day github 項目中,歡迎你們蒞臨指導 😊