ECMAScript 2015 支援了 extends
,至此 ECMAScript 能輕鬆實踐 Inheritance,但事實上也能由 Prototype Chain 實現。
Version
ECMAScript 2015
Extends
class Shape {
constructor(x, y) {
this.x = x
this.y = y
}
move(x, y) {
this.x = x
this.y = y
}
}
class Circle extends Shape {
constructor(x, y, radius) {
super(x, y)
this.radius = radius
}
draw() {
return `Drawing a Circle at ${this.x}, ${this.y}, Radius: ${this.radius}`
}
}
let circle = new Circle(0, 0, 5)
circle.move(10, 10)
circle.draw() // ?
以 class 實踐 OOP 最經典的 Shape。
此範例在 C++ 經常出現,後來 Java 與 C# 亦常常使用此範例
第 1 行
class Shape {
constructor(x, y) {
this.x = x
this.y = y
}
move(x, y) {
this.x = x
this.y = y
}
}
- 使用
constructor
建立Shape
class 的 constructor move()
為Shape
的 instance method
12 行
class Circle extends Shape {
constructor(x, y, radius) {
super(x, y)
this.radius = radius
}
draw() {
return `Drawing a Circle at ${this.x}, ${this.y}, Radius: ${this.radius}`
}
}
- 使用
extends
繼承Shape
- 使用
super()
呼叫 parent class 的 constructor draw()
為Circle
的 instance method
22 行
let circle = new Circle(0, 0, 5)
circle.move(10, 10)
circle.draw() // ?
- 使用
new
建立circle
Object - 執行
move()
與draw()
instance method
這是完全 OOP 風格寫法,也再次證明 ECMAScript 為 multi-paradigm 語言,可完整實現 class-based 風格 OOP
儘管使用了 extends
,但可發現 draw()
在 circle
的 Prototype Object 上,而 move()
在 Circle.prototype
的 Prototype Object 上,因為 extends
為 synatic sugar,所以完全看不到 Prototype Chain。
Prototype Chain
function Shape(x, y) {
this.x = x
this.y = y
}
Shape.prototype.move = function(x, y) {
this.x = x
this.y = y
}
function Circle(x, y, radius) {
Shape.call(this, x, y)
this.radius = radius
}
Circle.prototype = Object.create(Shape.prototype)
Circle.prototype.constructor = Circle
Circle.prototype.draw = function() {
return `Drawing a Circle at ${this.x}, ${this.y}, Radius: ${this.radius}`
}
let circle = new Circle(0, 0, 5)
circle.move(10, 10)
circle.draw() // ?
ES6 雖然提供 class 寫法,但骨子仍是 prototype,class 只能算 syntatic sugar。
第 1 行
function Shape(x, y) {
this.x = x
this.y = y
}
建立 Shape()
constructor function,其中 this
指向將來 new
所建立的 Object。
因為使用 this
,因此要使用 function expression。
第 6 行
Shape.prototype.move = function(x, y) {
this.x = x
this.y = y
}
在 prototype
上建立 move()
instance method 以節省記憶體。
11 行
function Circle(x, y, radius) {
Shape.call(this, x, y)
this.radius = radius
}
建立 Circle()
constructor function。
super()
要以 Shape.call()
實現,並將目前 Circle
的 this
傳入取代 Shape
本身的 this
。
16 行
Circle.prototype = Object.create(Shape.prototype)
Circle.prototype.constructor = Circle
Prototype Chain 要實踐 Inheritance 關鍵在這兩行。
最直覺應該是將 shape
Object 指定給 circle
Object 的 __proto__
即可實踐 Inheritance。
但目前尚未建立 Object,只能從 constructor function 下手,也就是將 Object 指定給 Circle.prototype
。
因此使用 Object.create()
直接以 Shape.prototype
為 Prototype 建立 Object,然後再指定給 Circle.prototype
則完成 Inheritance。
但此時因為 Object 由 Shape.prototype
建立,其 constructor
property 指向 Shape()
,因此要以 Circle.prototype.constructor = Circle
修正其 constructor 為 Circle()
,如此 circle
Object 的 __proto__
才能藉由 circle.constructor
找到 Circle()
與其 Prototype Object。
23 行
let circle = new Circle(0, 0, 5)
circle.move(10, 10)
circle.draw() // ?
一樣使用 new
建立 circle
Object,用法完全一樣。
儘管由 extends
改成 Prototype Chain,但 circle
架構完全不變,也再次證明 extends
為 Prototype Chain 的 syntatic sugar。
Conclusion
- 兩種寫法的結果都一樣,也都建立了
circle
Object,並呼叫move()
與draw()
- Class 寫法需使用
this
、extends
與super
概念 - Prototype Chain 寫法需使用到
call()
,尤其以 prototype 實現extends
比較難懂