javascript

ES6之JavaScript的類

起源JS從建立之初就不支援類,也沒有把類繼承作為定義相似物件以及關聯物件的主要方式,這讓不少開發者感到困惑。而...

>

起源

JS 從建立之初就不支援類,也沒有把類繼承作為定義相似物件以及關聯物件的主要方式,這讓不少開發者感到困惑。而從 ES1 誕生之前直到ES5 時期,很多庫都建立了一些工具,讓 JS 顯得貌似能支援類。儘管一些 JS 開發者強烈認為這門語言不需要類,但為處理類而建立的程式碼庫如此之多,導致 ES6 最終引入了類。

ES5 中的仿類結構

JS 在 ES5 及更早版本中都不存在類。與類最接近的是:建立一個構造器,然後將方法指派到

該構造器的原型上。這種方式通常被稱為建立一個自定義型別。例如:

function PersonType(name) {
	this.name = name;
}
PersonType.prototype.sayName = function() {
	console.log(this.name);
};
let person = new PersonType("Nicholas");
person.sayName(); // 輸出 "Nicholas"
console.log(person instanceof PersonType); // true
console.log(person instanceof Object); // true

此程式碼中的 PersonType 是一個構造器函式,並建立了單個屬性 name 。 sayName() 方法被

指派到原型上,因此在 PersonType 物件的所有例項上都共享了此方法。接下來,使用 new

運算子建立了 PersonType 的一個新例項 person ,此物件會被認為是一個通過原型繼承了

PersonType 與 Object 的例項。

這種基本模式在許多對類進行模擬的 JS 庫中都存在,而這也是 ES6 類的出發點。

es6基本的類的宣告

類宣告以 class 關鍵字開始,其後是類的名稱;剩餘部分的語法看起來就像物件字面量中的

方法簡寫,並且在方法之間不需要使用逗號。作為範例,此處有個簡單的類宣告:

class PersonClass {
// 等價於 PersonType 構造器
constructor(name) {
this.name = name;
}
// 等價於 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Nicholas");
person.sayName(); // 輸出 "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"

es6中類關鍵字class本質是一種語法糖,而使用類實現的繼承其本質上就是原型的繼承.

為何要使用類的語法

  • 類宣告不會被提升,這與函式定義不同。類宣告的行為與 let 相似,因此在程式的執行到達宣告處之前,類會存在於暫時性死區內。
  • 類宣告中的所有程式碼會自動執行在嚴格模式下,並且也無法退出嚴格模式。
  • 呼叫類構造器時不使用 new ,會丟擲錯誤。
  • 試圖在類的方法內部重寫類名,會丟擲錯誤。

作為一級公民的類

在程式設計中,能被當作值來使用的就稱為一級公民( first-class citizen ),意味著它能作為參

數傳給函式、能作為函式返回值、能用來給變數賦值。 JS的函式就是一級公民(它們有時又

被稱為一級函式),此特性讓 JS 獨一無二

ES6 延續了傳統,讓類同樣成為一級公民。這就使得類可以被多種方式所使用。例如,它能

作為引數傳入函式:

function createObject(classDef) {
	return new classDef();
}
let obj = createObject(class {
	sayHi() {
	console.log("Hi!");
}
});
obj.sayHi(); // "Hi!

使用派生類進行繼承

ES6 之前,實現自定義型別的繼承是個繁瑣的過程。嚴格的繼承要求有多個步驟。例如,研

究以下範例:

function Rectangle(length, width) {
	this.length = length;
	this.width = width;
}
Rectangle.prototype.getArea = function() {
	return this.length * this.width;
};
function Square(length) {
	Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
	constructor: {
		value:Square,
		enumerable: true,
		writable: true,
		configurable: true
	}
});
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true

Square 繼承了 Rectangle ,為此它必須使用 Rectangle.prototype 所建立的一個新物件來

重寫 Square.prototype ,並且還要呼叫 Rectangle.call() 方法。這些步驟常常會搞暈 JS

的新手,並會成為有經驗開發者出錯的根源之一。

類讓繼承工作變得更輕易,使用熟悉的 extends 關鍵字來指定當前類所需要繼承的函式,即

可。生成的類的原型會被自動調整,而你還能呼叫 super() 方法來訪問基類的構造器。此處

是與上個例子等價的 ES6 程式碼:

class Rectangle {
constructor(length, width) {
	this.length = length;
	this.width = width;
}
getArea() {
	return this.length * this.width;
}
}
class Square extends Rectangle {
	constructor(length) {
		// 與 Rectangle.call(this, length, length) 相同
		super(length, length);
	}
}
var square = new Square(3);
console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true

使用 super() 時需牢記以下幾點:

  1. 你只能在派生類中使用 super() 。若嘗試在非派生的類(即:沒有使用 extends關鍵字的類)或函式中使用它,就會丟擲錯誤。
  2. 在構造器中,你必須在訪問 this 之前呼叫 super() 。由於 super() 負責初始化this ,因此試圖先訪問 this 自然就會造成錯誤。
  3. 唯一能避免呼叫 super() 的辦法,是從類構造器中返回一個物件。

總結

ES6 的類讓 JS 中的繼承變得更簡單,因此對於你已從其他語言學習到的類知識,你無須將其

丟棄。 ES6 的類起初是作為 ES5 傳統繼承模型的語法糖,但新增了許多特性來減少錯誤。

Facebook Profile photo
Written by Nat
This is the author box. A short description about the author of this article. Could be their website link, what they like to read about and so on. Profile