前言

在介紹完prototype之後,該是來理解JavaScriptclass,之前有提到說,JavaScript的繼承依靠的是Prototypal inheritance,但並不是所有的程式語言都是如此,我覺得要理解class的話要從這裡下手,那就開始吧。

其他程式語言的繼承

之前有提到過繼承,其實道理差不多,一個物件可以將自己的屬性跟方法給其他人使用,換句話說,一個物件可以去取用別人物件的屬性跟方法。

JavaScript中,這種可以被取用屬性跟方法的物件是原型物件,透過原型鏈來找到跟綁定,但是在其他語言中,像是java或是c++就會直接把這個可以被取用屬性跟方法的物件視為class,一個類別的概念。

關於類別的比喻,有一個我覺得很不錯,出處是在:JavaScript 概念三明治

class其實就是像建築物的設計圖一樣,而建築物就可以透過裡面所描述的概念來寫現做出來。

class(類別) => 設計圖。
object(物件) => 建築物。

然後有了 class(類別)就可以創造出許多的instance(實例),透過用new的方式,非常合理,但是JavaScript做不到。

原因如下:

JavaScript 完全沒有設計 class 的概念。

class 非彼 class

書上看到有這樣寫,不過沒寫原因不太懂,為了探究真相,我開始上網 google,下了一些像是what javascript no class?或是What are classes in Javascript?的關鍵字。

其中有一篇在 stackoverflow 上的Does JavaScript have classes?看到很多高手在談論這個議題,最後的結論是確實在JavaScript是沒有實作class的機制的。

對,然後你會發現今天的主題也是class,但這其實沒有衝突,因為都沒有錯,確實是沒有class但因為想要模擬其他程式語言可以使用class的特性,所以在 es6 之後,JavaScript特別添加了叫做class的關鍵字。

所以難道這就代表著JavaScript就此獲得了class?
我的答案是否定的,沒有的東西就是沒有,JavaScript從來就沒辦法靠著類別的方式來繼承東西,是依靠原型的,所以就算 es6 之後有了 class 的關鍵字,那也是用原型的實作,來看起來很像類別的實作而已,還是不一樣的。

而別的程式語言都用類別方式繼承好好的,為什麼JavaScript要特立獨行,使用跟別人不一樣的方式(原型),我的猜測是作者最初開始在設計JavaScript不想要把它設計的太難,這也是它為什麼看起來這麼不嚴謹的原因,這點從 JavaScript 僅用了 10 天就創造出來就可以看出。

雖然因為不想要弄的太複雜所以沒有引入 class 的機制,但還是需要有東西去把所有的物件連接再一起去運作,為此依舊得需要「繼承」的概念。

而上面有提到說,其他很多的程式語言,都是透過 new 的方式把一個 class 的東西給創造出來變成 instance。

但是JavaScript沒有 class,但是它想到了一個辦法。

那就是使用我們昨天才講到的 Constructor Function(構造函式),利用 constructor 當作像是 class 一樣,然後藉由去 new 它一樣可以創造出 instance。

可是不會完全跟 class 繼承一樣,會非常的不完全,因為利用 constructor 所創造的 instance 根本沒有辦法共享所有的 properies 跟 methods。

所以才會後來才會創造出,原型這個概念,藉由讓 constructor 獲得原型的這個屬性,真正的讓 constructor 所創造的每一個 instance 都可以共享這個原型物件裡面所有的 properies 跟 methods。

剛剛在那篇高手討論JavaScript到底有沒有 class 的文章中,最佳回答是這樣回答的,我覺得蠻合理的:

echnically, the statement “JavaScript has no classes” is correct.

Although JavaScript is object-oriented language, it isn’t a class-based language—it’s a prototype-based language. There are differences between these two approaches, but since it is possible to use JavaScript like a class-based language, many people (including myself) often simply refer to the constructor functions as “classes”.

大意是說,雖然JavaScript是一種 oop 的程式語言,但是不是基於class的語言,而是基於prototype的語言,雖然這兩種的方式有著差異的存在,但是其實可以把JavaScript也當作是基於class的語言(實際上不是),因此許多人也會把constructor簡單的看做是class

小結:JavaScript沒有class但是有object,大家所說在JavaScript的「class」實際上是object(constructor)。

好,那開始進入這篇文章的主題,Class(類別) => es6 新增關鍵字。

class 與語法糖

第一件事情,JavaScript中的Class是一個語法糖,然後語法糖其實我第一次聽到也不知道,所以我有去研究,先來解釋這個部分。

根據維基百科上面的解釋,語法糖是:

語法糖(英語:Syntactic sugar)是由英國電腦科學家彼得·蘭丁發明的一個術語,指電腦語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程式設計師使用。語法糖讓程式更加簡潔,有更高的可讀性。
出處: https://zh.m.wikipedia.org/zh-tw/%E8%AF%AD%E6%B3%95%E7%B3%96

好的,其實就是一個可以用更簡單的方式來表達語法的做法,所以話拉回來,Class 這個語法糖確實也可以做到原型繼承的部分,但是它的原理依舊是使用原型的方式,然後其實也會使用到 Constructor 的概念,很高興這些前幾天都講過了,還沒看的可以回去前幾天複習再往下看,那馬上開始今天的介紹!

直接來做例子來了解其原理。

由於ES6之後才有Class,所以之前沒有Class時,會比較常使用Constructor來實現原型繼承,ES6之後有了Class會用Class,畢竟是語法糖,方便直覺很多,接下來就來兩種都做做看。

Constructor VS Class

Constructor先攻:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Food(fruit, color) {
this.fruit = fruit;
this.color = color;
}

Food.prototype.saySell = function () {
console.log("快來買蘋果!!!");
};

const apple = new Food("apple", "red");

apple.saySell();
//快來買蘋果!!!

我首先寫了一個準備要被new建立的函式,第一個英文字母記得大寫(細節),然後透過原型的方式,把方法指定給Food這個原型裡面,接著用new來建立構造函式,讓我的apple這個新的變數,也可以使用saySell這個方法 => 快來買蘋果!!!

Class的回合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Food {
constructor(fruit, color) {
this.fruit = fruit;
this.color = color;
}

saySell() {
console.log("快來買蘋果!!!");
}
}

const apple = new Food("apple", "red");

apple.saySell();

這是一個使用了ES6 Class的神奇方法,跟上面差不多,不過仔細上可以發現許多優點。

先來說明使用Class的流程,一開始使用Class這個關鍵字來創,然後Class後面是名稱,一樣要細節大寫,不同的是,內部就會直接塞一個constructor的函式,而這個函式的方法,就直接寫在了裡面。

需要方法的話,就直接把方法給塞進去Class裡面就好了,就在constructor的函式下方。

這是一件十分酷的事情,因為我就不需要自己再寫什麼.prototype把方法塞進去,Class就會自動幫我完成這件事情,語法糖,讚。

因為實驗精神的關係,來證明吧:

1
2
console.log(apple.__proto__ === Food.prototype);
// true

結果是 ture,確實有原型繼承到。

這一切都跟單純使用Constructor的方法差不多,但是使用Class會簡單一些,直覺一些,乾淨一些,結論就是多多使用Class

基本 Class 要知道

今天的部分來做一個小總結,由上面可以知道,其實Class的語法有一個固定模式,把它列出來的話會長這個樣子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass {
//就是你想的那個構造函式
constructor() { ... }

//這邊直接寫class的方法
method1() { ... }
method2() { ... }
method3() { ... }
...

//剛new就會自動調用constructor()
const vic = new MyClass( ... );

//方法要用自己new後再調用
vic.method1();
}
  1. class後面的名字不用(),它是用來創造instance不是用來執行的。
  2. 不用()但是名字要大寫,跟之前constructor差不多概念。
  3. new 之後會自動調用 constructor()裡的方法,所以可以藉由 constructor()初始化所創造出來新的instance
  4. 其他的方法不會自動調用,要用自己再 new 之後去調用它。

reference

[1] 原型基礎物件導向
[2] 維基百科 - 物件導向程式設計
[3] What is the difference between classical and prototypal inheritance?