語法糖 Syntactic sugar,指電腦語言中添加的某種語法,這種語法對語言的功能沒有影響,但是讓程式更加簡潔,有更高的可讀性。
而 class 稱為 ES6 新增的語法糖,也意味著沒有新功能的變動,而是對於物件的繼承與建造有更方便的寫法,今天就來比較一下使用 constructor 與 ES6 增加的 class 有什麼寫法上的差異吧!
圖片來源 CLEANPNG
本文段落分為:
七大差異比較:
函式可以在定義呼叫使用,這稱為 function declaration 有 hoisting 的緣故,但是在 class 使用 class declaration 並不會有 hoisting 效果,必須先定義 class 後才能呼叫!否則拋出 error 訊息 not defined
,這個錯誤訊息意味著不允許在宣告之前實體化 instantiate 一個類別。
// constructor
let cookie1 = new Cookie(); // chocolate
function Cookie(){
console.log('chocolate')
}
// class
let cake1 = new CakeClass(); // error; CakeClass is not defined
class Cake {
constructor(){
console.log('tiramisu')
}
}
建立新物件使用 constructor 時,若忘記加上 new
關鍵字,雖然不會產生新的物件但也不會報錯,而是變成的 function 賦值,若沒有 return 印出會是 undefined;
但在 class 使用上規定要加上new
,否則拋出錯誤訊息
Uncaught TypeError: Class constructor Cake cannot be invoked without 'new'
// constructor
function Cookie(flavor) {
this.flavor = flavor;
}
let cookie1 = Cookie('mocha'); // 沒有加上 new,cookie1 印出 undefined
let cookie2 = new Cookie('chocolate'); // cooke2 -> Cookie {flavor: 'chocolate'}加上 new 後,Cookie 為 constructor 建立出實例 cookie2
// class
class Cake {
constructor(flavor) {
this.flavor = flavor;
}
}
let cake1 = Cake('cheezeCake'); // error; 必須加上 new
let cake2 = new Cake('tiramisu'); // cake2 -> Cake {flavor: 'tiramisu'}
constructor 的共享屬性需要透過 constructor.prototype
的方式寫入原型物件中,class 可以直接寫在 class body 內 {}
,會自動寫入該類別的 prototype 中。
// constructor
function Cookie(flavor){
this.flavor = flavor;
}
Cookie.prototype.price = (cost) => `NTD ${cost * 1.5}`
let cookie1 = new Cookie('chocolate')
console.log( cookie1 ) // {flavor: 'chocolate'}
console.log( cookie1.price(50)) // NTD 75
// class
class Cake {
constructor(flavor){
this.flavor = flavor;
}
price(cost) {
return `NTD ${cost * 1.5}`
}
}
let cake1 = new Cake('tiramisu')
console.log( cake1.price(50) ) // NTD 75
static
靜態方法靜態方法只能透過 constructor 或是 class 呼叫,並不會繼承,因此由 constructor、class 建立的實例 instance 無法取得,constructor 可以使用 .
方法寫入; 而 class 只要加上 static
關鍵字就可直接在 class body 內定義。
// constructor
function Cookie(flavor) {
this.flavor = flavor;
}
Cookie.secret = ()=> {console.log('老闆娘很票釀')}
let cookie1 = new Cookie('chocolate')
cookie1.secret() // error
Cookie.secret() // 老闆娘很票釀
// class
class Cake {
constructor(flavor) {
this.flavor = flavor;
}
price(cost) {
return `NTD${cost* 1.5 }`
}
static secret(){
console.log('老闆娘很票釀')
}
}
let cake1 = new Cake('tiramisu')
cake1.secret() // error
Cake.secret() // 老闆娘很票釀
extends
擴增原型鏈在 ES6 以前,若想擴增原型鏈,必須在 constructor 的原型中創立一個新物件,再指定繼承母階的 prototype,並且須重新設定 constructor 屬性指回自己; 而 class 大大簡化了這個步驟,透過 extend
及 super
語法綁定 parentClass 。
// constructor
function Pastry(category){
this.category = category;
}
Pastry.prototype.owner = ()=> {console.log('Hoo')}
function Cookie(flavor){
this.flavor = flavor;
}
// 新增 Pastry 到原型鏈上
Cookie.prototype = Object.create(Pastry.prototype)
Cookie.prototype.constructor = Cookie
// 之後
let cookie1 = new Cookie('chocolate')
cookie1.owner() // Hoo,成功繼承到 Pastry prototype
cookie1.__proto__.__proto__ === Pastry.prototype // true
// class 使用 extend 和 super 簡化寫法
class Cake extends Pastry{
constructor(flavor){
super('cake');
this.flavor = flavor;
}
}
let cake1 = new Cake('tiramisu')
cake1.owner() // Hoo
cake1.__proto__.__proto__ === Pastry.prototype // true
使用 class 不管是 declaration 或 expression 都會自動開啟嚴格模式,就算沒有加上 use strict
字樣,所有嚴格模式禁止行為在 class 區塊內都會拋出錯誤!
嚴格模式禁止行為請看-> D4 - 加鹽不加價 嚴格模式開啟
以嚴格模式下禁止使用的保留字 package
測試看看
// 一般 function declaration
function func(){
let package = '一般模式 package 不是保留字';
console.log(package)
}
func() // 一般模式 package 不是保留字
// class declaration
class ClassFunc {
constructor(){
let package = '印不出來';
console.log(package)
}
}
// Uncaught SyntaxError: Unexpected strict mode reserved word
在 Chrome DevTools 印出可以很明確的知道這個實例是由 constructor 還是 class 建立。
由 constructor 建立的實例,[[Prototype]] 屬性下的 constructor 顯示為 function
由 class 建立的屬性,[[Prototype]] 屬性下的 constructor 顯示為 class
difference | constructor | class |
---|---|---|
code | function 開頭 | class 開頭,實例內容寫在 constructor 下 |
hoisting | function declaration 有 hoisting | 沒有 hoisting |
new | 沒有加上 new 不會生成新物件,但也不會報錯 |
必須加上 new 才能呼叫 class,否則拋出錯誤訊息 |
嚴格模式 | 加上 use strict 才是嚴格模式 |
class 函式自動成為嚴格模式 |
原型方法 | 原型方法寫在 prototype 物件內 | 原型方法寫在 class body 內 |
靜態方法 | 靜態方法須另外寫在 constructor 屬性內 | 在 class body 中加上 static 就可增加靜態方法 |
繼承 | 另外指派 prototype 來增加繼承對象 | 使用 extends 增加 parentClass |
Devtools | 顯示 function | 顯示 class |
class
比較純粹的 class-based 物件導向語言,物件是由 class 創造,因此所有方法及成員須一開始寫在 class 內,無法在 class 外的區域定義,因此當實例已經建立完成,就無法再額外新增屬性。
而 JavaScript 就不限定啦!基於 prototype 設定,物件被創立後可以自己再新增內部屬性,或是跟著繼承來的 prototype 內容變動。
舉例:建立一個 Employee class 並 new 出實例 john,分別以 Java 及 JavaScript 寫法做比較:
// 建立 Employee class
public class Employee {
public String employeeName = "Default name";
public int employeeId = 0;
public Employee(String name, String id) {
System.out.println("Employee class instantiated");
this.employeeName = name;
this.employeeId = id ;
}
public void printEmployee() {
System.out.println("Name: " + employeeName + " Id: " + employeeId);
}
setEmployeName(String name, String id){}
getEmployeeName(String name, String id){}
}
// new + 建構子建立實例
Employee john = new Employee("John”, 123); // output: "Employee class instantiated"
john.printEmployee(); // output: "Name: John Id: 123"
class Employee {
constructor (name, id) {
this.name = name;
this.id = id;
}
getDetails() {
return `${this.name} , ${this.id}`;
}
}
// new + class 建立物件
let john = new Employee("John", 123);
// john 建立後可以繼續增加屬性
john.saysHello = function() {
console.log(this.name + " says: Hello World!");
}
john.getDetails() // "John , 123"
john.saysHello() // John says: Hello World!
忍者JavaScript 開發技巧探秘 2
MDN - class
As a JS Developer, This Is What Keeps Me Up at Night
The Difference Between Java And JavaScript
從物件導向開始到原型鏈、 class 與 constructor 比較,這系列終於結束!
終於又把一個大坑填平,填坑挖坑的過程真是又累又滿足啊~