[ 筆記 ] JavaScript 進階 08 - new、extends、super、封裝


Posted by krebikshaw on 2020-09-28

在了解了 Prototype Chain 之後,我們要來利用物件導向的概念,近一步了解在 ES5、 ES6 之間實作的差異,以及 new、super、封裝 的意義是什麼。

new 關鍵字 : 模擬 new 在背後幫你做的事

上一篇文章分享過要建立一個新的物件來繼承另一個資源的方法時,需要用到 new 這個關鍵字,現在我們要來探討,當我們使用 new 時,背後系統幫我們做了什麼

接下來以一個自己寫的 function newDog('yello'),來模擬 new Dog('yello') 在做的事:

const yello = newDog('yello');

white.sayHi();
yello.sayHi();

首先目的是通過自己寫的 newDog 產生一個 instance yello,希望這個 instance 會有 跟用 new 出來的 instance white 有一樣的屬性跟方法。

所以目標放在如何寫出 newDog() 來模擬 new 這個關鍵字背後做的事情。

function newDog(name) {
  const obj = {};                 // 1.
  Dog.call(obj, name);            // 2.
  obj.__proto__ = Dog.prototype;  // 3.
  return obj;                     // 4.
}
  • new 背後做的事:
    1. 產生一個新 object => obj
    2. 把 Dog 當作 constructor,將 this 指向 obj,同時把參數 name 丟進去
      => 因為 Dog 只有一個參數所以用 call,如果是兩個以上的參數就可以用 apply 放陣列
    3. obj.__proto__ 指向 Dog.prototype
    4. 回傳 obj

在 ES5 時,通常是把 function 當成 constructor 來用,稱為「 建構函式 」,而共用的 method 則會放在 prototype 新增,可以避免在每個 instance 重複新增內容一樣的 method。

而關鍵在於 new 關鍵字,用 new 出來的 function, JS 會在背後幫你做好一連串的機制。

接下來我們來看,到了 ES6 之後,寫法上面會變成什麼樣子

ES5 VS ES6

ES5 : 建構函式 + prototype

function Dog(name) {
  this.name = name;
}

Dog.prototype.sayHello = function() {
  console.log(this.name, ' Hello');
}

const happy = new Dog('happy');
happy.sayHello();

const white = new Dog('white');
white.sayHello();

ES6 : Class

class Dog {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(this.name, ' Hello');
  }
}
const happy = new Dog('happy');
happy.sayHello();

const white = new Dog('white');
white.sayHello();

在 ES6 之後,開始有了 Class 這個語法糖可以使用。當我們想要建立一個物件導向的資源時,感覺有點像宣告一個 class 的物件,把所有跟這個物件相關的 property & method 通通包在一起的感覺。

如此一來只要是在 class 之中宣告的 function 都會被加入 prototype。

extends 繼承

當我們建立好ㄧ個 class 之後,可以以這個 class 為基礎,新建一個類似的 class。就像我想要以「狗」這個類別為基礎,新建一個類別叫「柴犬」,它可以繼承「狗」的屬性及方法。

而這個新建語法就是 extends

  • class 柴犬 extends 狗
class Dog {
  ...
}

class TaiwansDog extends Dog {
  ...
}

super

在 ES6 使用繼承時,如果要將 子類別的 constructor 改寫,需要先呼叫 super() 這個函式,否則會跳出錯誤:Must call super constructor in derived class before accessing 'this' or returning from derived constructor。

class Dog {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    console.log(this.name, 'Hi');
  }
}

class TaiwansDog extends Dog {
  constructor(name) {
    super(name); // => 記得要先 super()
    this.sayHi();
  }
}

const white = new Dog('white');
const black = new TaiwansDog('black');

封裝

先前在 Closure 的概念當中有提到,如果你希望一個變數是不能被任意賦值的,可以將它封裝起來,使外部無法任意存取。

  • ES5 - function 模擬 Class 版 ( real private )
function Wallet(init) {
  let money = init;

  this.getMoney = function() {
    return money;
  }
};

const wallet = new Wallet(100);
console.log(wallet.getMoney());
console.log(wallet.money); // => 存取不到,真正的 private
  • ES6 - constructor 版 ( real private )

一般在 class 裡面初始化變數,會在 constructor 裡面用 this.variable = variable 的寫法,用這個方式宣告出來的變數,是能夠從外部存取的

class Wallet {
  constructor(num) {
    this.money = num;
  }
};

const wallet = new Wallet(100);
console.log(wallet.money)  // 100

但是若是在 constructor 裡面用 varlet 或者 const 宣告變數,只要在 constructor 外部就通通存取不到了,所以需要存取 private property 的 function 只能一並寫在 constructor 裡面。

  • private property 在命名的時候習慣性以 _ 開頭
class Wallet {
  constructor(num) {
    var _money = num;
    this.getMoney = () => _money;
    this.setMoney = (newNum) => _money = newNum;
  }
  getMoney = () => _monsy;  // 像這樣寫在 constructor 外面的 function 就存取不到 _money 了
};

const wallet = new Wallet(100);
console.log(wallet.getMoney());
console.log(wallet._money); // => 存取不到,真正的 private

現在 ES6 還沒有看到通用的方法,最簡單的方法其實可以再 private property or method 的名稱加上 _ 前綴,同事們可以很簡單看出這是個私有屬性,沒事不要去動它。


#OOP #new #super #extends







Related Posts

GoogleChrome開發者工具教學 - Network

GoogleChrome開發者工具教學 - Network

每日心得筆記 2020-06-12

每日心得筆記 2020-06-12

展開與其餘參數

展開與其餘參數


Comments