iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
Modern Web

前端蛇行撞牆記系列 第 15

Day15 前端蛇行撞牆記 - class v.s constructor

  • 分享至 

  • xImage
  •  

前一篇介紹了constructor,現在來介紹ES6開始有的class。

  • JS其實沒有真正的class實體,class宣告出來的實體是function。
  • class的繼承是透過prototype chain所達成的。(其他語言是透過class chain)

學過原型鏈之後再來看class,應該可以理解class做了什麼事情,現在來建一個class:

class Dog {
  sayHello() {
    console.log(`hello`);
  }
}

const d = new Dog();

d.sayHello();
// hello
console.log(d);
// Dog {}

當d被變成這個class Dog的instance的時候,他就可以使用sayHello()

這個sayHello() 是不是就很像 constructor的prototype?

現在d物件雖然可以用function,但他還是個空物件,該怎麼放東西進去?
那就直接放constructor,然後放參數進去就會變成他的property了。

class Dog {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  setName(name) {
    this.name = name;
  }
  getName() {
    console.log(`${this.name} say hello to you`);
  }
}

const d = new Dog("dd", 22);

console.log(d);
// Dog { name: 'dd', age: 22 }

d.setName("cc");
d.getName();
// cc say hello to you
  • 來比對constructor的寫法:
function Dog(name, age) {
  this.name = name;
  this.age = age;
}

Dog.prototype.getName = function () {
  console.log(`${this.name} say hello to you`);
};

Dog.prototype.setName = function (value) {
  this.name = value;
};

const c = new Dog("cc", 23);

console.log(c);
// Dog { name: 'cc', age: 23 }
c.setName("ee");
c.getName();
// ee say hello to you

所以class跟constructor的差異看起來像是prototype、property都可以直接設定在class裡面,不用像constructor一樣prototype要另外設。

class的方式比較好閱讀,但底層還是用constructor的方式在實作的。

class與constructor function的差異

  • 函式可以被hoisting,但class不會
    • 雖然class Dog, constructor Dog都是function,但只有constructor有hoisting的效果,如果把建立class Dog instance放在前面會出現TDZ。
  • 建立一個class Dog並不會建立一個全域物件特性
      • 可以看到constructor會被建立在全域物件上,但class不會。(雖然兩個都是function)
      • [你所不知道的JS]class並非一個真正的實體(entity),而是包裹了其他具體實體(例如函式與特性)並把他們綁在一起的詮釋概念(meta consept)
  • 一定要使用new來呼叫class
  • 在class裡面的所有程式碼,預設都會進去嚴格模式
  • class裡面定義的方法都是不能被列舉的。
    • 不能被列舉的就是Object.keys(),如果使用Object.getOwnPropertyNames()就可以。
    console.log(Object.getOwnPropertyNames(Dog.prototype));
    
    // [ 'constructor', 'setName', 'getName' ]  
    
    • constructor則都可以被列舉出來
    console.log(Object.keys(c.__proto__));
    //也可以這樣寫
    console.log(Object.keys(dog.prototype));
    // [ 'getName', 'setName' ]
    

class的靜態方法 static

  • 只要在class裡面的方法前面加了static關鍵字,這個方法就不會被instance所繼承。
class Dog {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  setName(name) {
    this.name = name;
  }
  getName() {
    console.log(`${this.name} say hello to you`);
  }
  static sayHello() {
    console.log(`${this.name} Hello`);
  }
}

const d = new Dog("dd", 22);
d.sayHello();
// d.sayHello is not a function
Dog.sayHello();
// Dog Hello

Extends

要加s

  • 建造一個新class使用extends 舊class,就會讓新class繼承舊class的屬性或方法。
  • 如果是新class透過這個class去extends的話,父class的靜態方法也會被子class繼承。

class Cow extends Dog {
    // 裡面沒有東西
}

Cow.sayHello();
// Cow Hello
// 可以使用class Dog的靜態方法
  • 在瀏覽器可以看到class Cow.prototype的[[prototype]]是class Dog
  • 使用靜態方法的sayHello()不屬於Dog的prototype,而是他的方法。
  • 注意這裡是子class可以直接使用sayHello(),但不代表子class的instance可以用喔!

super

MDN上的解釋:

The super keyword is used to access properties on an object literal or class's [[Prototype]], or invoke a superclass's constructor.
翻譯:super 關鍵字被使用於通過函式存取父層constructor。

  • 當作函式的時候要放在constructor裡面,在constructor的super()會自動參考父constructor(以子class this的情境呼叫一個父constructor)
  • 所以在super()被呼叫之前都不能取用沒有定義的this
  • super可以當作函式也可以當作對象。

現在有一個class Dog

class Dog {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  setName(name) {
    this.name = name;
  }
  getName() {
    console.log(`${this.name} say hello to you`);
  }
  static sayHello() {
    console.log(`${this.name} Hello`);
  }
}

再讓class Cow extends Dog,並新增一個屬性weight。其他兩個屬性想要用繼承class Dog的方式,就只有新增this.weight = weight

class Cow extends Dog {
  constructor(name, age, weight) {
    this.weight = weight;
  }
}

const cat = new Cow("cat", 15, 10);
console.log(cat);
// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

結果卻報錯,是因為現在有多了一個新的屬性,所以需要再打constructor的this.weight = weight,但是前面沒有用super()去呼叫class Dog的constructor,就等於現在的this不知道要綁定class Dog的constructor還是class Cow的constructor。

MDN上的節錄:

...the super keyword may appear as a "function call" (super(...args)), which must be called before the this keyword is used, and before the constructor returns. It calls the parent class's constructor and binds the parent class's public fields, after which the derived class's constructor can further access and modify this.
翻譯:super在當成function使用的時候,一定要在this前或consturctor被返回前被呼叫。他呼叫父class的constructor綁定父class的公共區域,再這之後子class才能去存取、修改this

如果今天沒有新增新的屬性的話是可以直接用到class Dog的constructor。

class Cow extends Dog {
}

const cat = new Cow("cat", 15);
console.log(cat);
// Cow { name: 'cat', age: 15 }

正確方法:

  • 要使用this前需要先使用super()來呼叫父class的constructor
class Cow extends Dog {
    constructor(name, age, weight) {
        super(name, age); // => 要放在this前面
        this.weight = weight;
    }
}

const cat = new Cow("cat", 15, 10);
console.log(cat);
// Cow { name: 'cat', age: 15, weight: 10 }

class Cow裡面沒有去定義this.name = name,但他卻可以透過super()去找class Dog裡面定的this.name = name,可以產生同樣的結果。

  • super()當作物件使用的話,就會參考父物件,就能存取他的特性或方法。
    (這裡先把static sayHello()拿掉了,不然讀不到)
class Dog {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  setName(name) {
    this.name = name;
  }
  getName() {
    console.log(`${this.name} say hello to you`);
  }
  sayHello() {
    console.log(`${this.name} Hello`);
  }
}

class Cow extends Dog {
  constructor(name, age, weight) {
    super(name, age);
    this.weight = weight;
  }

  sayGoodbye() {
    super.sayHello(); // => 當成物件的方式
    console.log(`${this.name} Goodbye`);
  }
}

const cat = new Cow("cat", 15, 10);
cat.sayGoodbye();
// cat Hello
// cat Goodbye

在方法裡面使用super.sayHello()就會去找到父class的方法並在這裡使用,所以可以印出父class sayHello() 的cat Hello,也可以印出自己的方法 cat Goodbye。

總結

  • class, constructor 都是 function
  • class 是靠prototype chain做出來的
  • class 把constructor寫在裡面一樣產生instance
  • extends之後 super可以當函式使用,也可以當作物件使用,都會指向父class
  • super()在constructor出現等於是呼叫父class constructor。

終於學到class拉!寫法上比constructor簡單清楚,又多了extends, super可以使用,一邊看也一邊練習會更清楚喔!

明天見拉~


參考資料:008
[教學] 深入淺出 JavaScript ES6 Class (類別)
胡立 JS201


上一篇
Day14 前端蛇行撞牆記 - 由__proto__串起的原型鏈
下一篇
Day16 前端蛇行撞牆記 - 替github, gitlab設定不同SSH key
系列文
前端蛇行撞牆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言