天天一個小技巧:Javascript中定義私有屬性(Private Properties)

logo

和不少高級語言不一樣,JavaScript 中沒有 publicprivateprotected 這些訪問修飾符(access modifiers),並且長期以來也沒有私有屬性這個概念,對象的屬性/方法默認都是public的。雖然目前 class 的私有屬性特性已經進入了 Stage3 實驗階段(Spec),經過 Babel 已經可使用,而且 Node v12 中也增長了對私有屬性的支持,但這並不妨礙咱們用 JS 的現有功能實現一個私有屬性特性,以加深對這一律唸的理解。git

私有屬性(方法)的意義在於將模塊的內部實現隱藏起來,而對外接口只經過public成員進行暴露,以減小其餘模塊對該模塊內部實現的依賴或修改,下降模塊的維護成本。

IIFE 實現

IIFE(當即執行函數) 你們應該耳熟能詳了,IIFE 常常被用來:github

  1. 定義一個自執行的匿名函數
  2. 建立一個局部做用域,避免對全局產生污染

基於以上特性,用 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

看起來還行,可是該實現方式須要在構造函數中定義 gettersetter 方法,這兩個方法是綁定在實例上而不是原型上的,若是私有屬性增長會致使實例方法暴增,對內存不太友好。prototype

Class實現

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是怎麼實現的:

babel

發現主要思路竟然使用 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 項目中,歡迎你們蒞臨指導 😊

相關文章
相關標籤/搜索