iT邦幫忙

2022 iThome 鐵人賽

DAY 6
0

前言

在介紹完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先攻:

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的回合:

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就會自動幫我完成這件事情,語法糖,讚。

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

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

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

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

基本Class要知道

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

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?


上一篇
JS之路 Day05 - Constructor Function(構造函式)
下一篇
JS之路 Day07 - 語法糖Class(下)
系列文
JavaScript 之路,往前邁進吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言