對於 class
這個語法糖,以下再繼續進行一些考察。
this
的考察class Circle1 {
draw() { return this }
}
function Circle2() {
this.draw = function () { return this }
}
const c1 = new Circle1();
const c2 = new Circle2();
const c3 = {
draw: function () { return this }
}
// Method Call
console.log(c1.draw() === c1); // true
console.log(c2.draw() === c2); // true
console.log(c3.draw() === c3); // true
// Function Call
const draw1 = c1.draw;
console.log(draw1()); // undefined,而非指向全域物件
const draw2 = c2.draw;
console.log(draw2()); // Window,即全域物件
const draw3 = c3.draw;
console.log(draw3()); // Window,即全域物件
從上面可以看到,不論是建構子函式或者 class
,兩者的實例在調用時都是指向調用當下的環境,也就是 c1
和 c2
,跟 c3
互相對照,this
依循隱含綁定規則指向調用的物件。
但將物件內部函式的參考複製出來執行後,draw2()
跟 draw3()
同樣指向了全域物件,draw1
則指向 undefined
,等同於 class
的 this
綁定默認使用嚴格模式,這點是 class
與建構子函式不同的部分。
函式宣告(Function Declaration)會有提升(Hoisting),因此可以在宣告前呼叫:
sayHello(); // Hello
function sayHello() { console.log("Hello") }
函式表達式(Function Expression)則在執行時期才會賦值,因此無法在宣告前呼叫:
sayGoodbye();
// Cannot access 'sayGoodbye' before initialization
const sayGoodbye = function() { console.log("sayGoodbye") }
class
的提升class
宣告(Class Declaration)並沒有提升,無法在宣告前調用:
const c = new Circle()
// Cannot access 'Circle' before initialization
class Circle { }
class
表達式(Class Expression)也一樣,宣告前無法調用:
const s = new Square()
// Cannot access 'Square' before initialization
const Square = class { };
class
總結class
關鍵字帶來的優點extend
明確宣告繼承,而且實踐的方法非常自然,不像用 Object.create
、 __proto__
或 Object.setPrototypeOf
來替換 prototype
指向的物件那樣,充滿複雜而令人困惑的邏輯super
提供了實踐「相對多態」的能力,也就是讓物件能夠引用原型鍊上「上一個層級」的同名方法class
語法僅對繼承方法進行了優化,屬性則沒太大改變。這點從另一個角度來看,由於屬性大部分時候不應該存在於原型鍊末端以外,也就是實例以外的地方,所以反而能夠防止錯誤class
關鍵字帶來的缺點:prototype
語法class
將「類機制」在 JS 中模擬得更加維妙維肖,同時也將物件彼此的鏈結關係與行為委託隱藏在 class
語法之下,因此衍生出許多更難以察覺的陷阱