深刻理解ES6--9.JS的類

主要知識點:類聲明、類表達式、類的重要要點以及類繼承編程

JS的類的知識點

1. 類的聲明

基本的類聲明數組

類聲明以class關鍵字開始,其後是類的名稱;類中的方法就像是對象字面量中的方法簡寫,而且方法之間不須要使用逗號:安全

class PersonClass{


	constructor(name){
		this.name = name;
	}
	sayName(){
		console.log(this.name);
	}
}

let person = new PersonClass("hello class");
person.sayName();
複製代碼

類聲明語法容許使用constructor直接定義一個構造器,而不須要先定義一個函數,再把它當作構造器來使用。類中的方法使用的函數簡寫語法,省略了關鍵字function函數

使用class關鍵字來定義一個類型,有這樣幾個要點:性能

  1. 類聲明不會被提高,這與ES6以前經過函數定義不一樣。類聲明與使用let定義同樣,所以也存在暫時性死區;
  2. 類聲明中的全部代碼會自動運行在嚴格模式下,而且沒法退出嚴格模式;
  3. 類的全部方法都是不可枚舉的;
  4. 類的全部內部方法都沒有[[Constructor]],所以使用new來調用他們會拋出錯誤;
  5. 調用類構造器時不使用new,會拋出錯誤;
  6. 試圖在類的內部方法中重寫類名,會拋出錯誤;

2. 類表達式

類與函數有類似之處,都有兩種形式:聲明與表達式。函數聲明與類聲明都以關鍵詞開始(分別是function和class),以後就是標識符(即函數名或者類名)。若是須要定義匿名函數,則function後面就無需有函數名,相似的,若是採用類表達式,關鍵是class後也無需有類名;this

基本的類表達式spa

使用類表達式,將上例改爲以下形式:prototype

let PersonClass = class {


	constructor(name){
		this.name = name;
	}
	sayName(){
		console.log(this.name);
	}
	
}

let person = new PersonClass("hello class");
person.sayName(); //hello  class
複製代碼

示例代碼中就定義了一個匿名的類表達式,若是須要定義一個具名的類表達式,只須要像定義具名函數同樣,在class關鍵字後面寫上類名便可。3d

3. 類的重要要點

做爲一級公民的類code

在編程中,可以被看成值來使用的就成爲一級公民(first-class citizen)。既然都看成值使用,就說明它可以做爲參數傳遞給函數、能做爲函數的返回值也能用來給變量賦值。JS中的函數是一等公民,類也是一等公民。

例如,將類做爲參數傳遞給函數:

function createObj(classDef){
	return new classDef();
}

let obj = createObj(class{
	sayName(){
		console.log('hello'); //hello
	}
})

obj.sayName();
複製代碼

**類表達式另外一個重要用途是實現當即調用類構造器以建立單例。**語法是使用new來配合類表達式使用,並在表達式後面添加括號():

//當即調用構造器

let person = new class{
	constructor(name){
		this.name = name;
	}
	sayName(){
		console.log(this.name);
	}
}('hello world');

person.sayName(); //hello world
複製代碼

訪問器屬性

自有屬性須要在類構造器中建立,而類還容許建立訪問器屬性。爲了建立一個getter,要使用get關鍵字,並要與後面的標識符之間留出空格;建立setter使用相同的方式,只須要將關鍵字換成set便可:

class PersonClass{
	constructor(name){
		this.name = name;
	}
	get name(){
		return name; //不要使用this.name會致使無限遞歸
	}

	set name(value){
		name=value; //不要使用this.value會致使無限遞歸
	}
}
let person = new PersonClass('hello');
console.log(person.name); // hello
person.name = 'world';
console.log(person.name); //world
let descriptor = Object.getOwnPropertyDescriptor(PersonClass.prototype,'name');
console.log('get' in descriptor); //true
複製代碼

需計算屬性名

對象字面量和類之間的類似點有不少,類方法與類訪問器屬性都能使用需計算屬性名的方式,語法與對象字面量中需計算屬性名同樣,都是使用方括號[]來包裹表達式:

//需計算屬性名
let methodName ='sayName';
let propertyName = 'name';

class PersonClass{
	constructor(name){
		this.name = name;
	}
	get [propertyName](){
		return name;
	}
	set [propertyName](value){
		name = value;
	}
	[methodName](){
		return console.log(this.name);
	}
}
let person = new PersonClass('hello world');
person.sayName(); //hello world
console.log(person.name); //hello world
複製代碼

生成器方法

在對象字面量中定義一個生成器:只須要在方法名前附加一個星號*便可,這一語法對類一樣有效,容許將類的任意內部方法編程生成器方法:

//生成器方法:

class GeneClass{

	*generator(){
		yield 1;
		yield 2;				
	}
}

let obj = new GeneClass();
let iterator = obj.generator();
console.log(iterator.next().value); //1
console.log(iterator.next().value); //2
console.log(iterator.next().value); //undefined
複製代碼

可迭代對象用於Symbol.iterator屬性,而且該屬性指向生成器函數。所以,在類定義中一樣可使用Symbol.iterator屬性來定義生成器方法,從而定義出類的默認迭代器。同時也能夠經過生成器委託的方式,將數組、Set、Map等迭代器委託給自定義類的迭代器:

class Collection {
	constructor() {
		this.items = [];
	} 
	*[Symbol.iterator]() {
		for(let item of this.items){
			yield item;
		}				
	}
} 
let collection = new Collection();
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
	console.log(x);
}
輸出:1 2 3
複製代碼

靜態成員

ES6的類簡化了靜態成員的建立,只要在方法與訪問器屬性的名稱前添加static關鍵字便可:

class PersonClass {
	// 等價於 PersonType 構造器
	constructor(name) {
		this.name = name;
	}
	static create(name) {
		return new PersonClass(name);
	}
}

let person = PersonClass.create("Nicholas");
複製代碼

經過在方法前加上static關鍵字,使其轉換成靜態方法。能在類中的任何方法與訪問器屬性上使用 static 關鍵字,惟一限制是不能將它用於 constructor 方法的定義。靜態成員不能用實例來進行訪問,始終須要用類自身才能訪問它們。

類繼承

使用關鍵字extends能夠完成類繼承,同時使用super關鍵字能夠在派生類上訪問到基類上的方法,包括構造器方法:

//類繼承

class Rec{
	constructor(width,height){
		this.width = width;
		this.height = height;
	}

	getArea(){
		return this.width*this.height;
	}

}

class Square extends Rec{
	constructor(width,height){
		super(width,height);
	}

}

let square = new Square(100,100);
console.log(square.getArea()); //10000
複製代碼

關於類繼承,還有這樣幾個要點:

  1. **在派生類中方法會覆蓋掉基類中的同名方法,**例如在派生類Square中有getArea()方法的話就會覆蓋掉基類Rec中的getArea()方法;
  2. 若是基類中包含了靜態成員,那麼這些靜態成員在派生類中也是可使用的。注意:靜態成員只能經過類名進行訪問,而不是使用對象實例進行訪問

從表達式中派生類

在ES6中派生類最大的能力就是可以從表達式中派生類,只要一個表達式可以返回的對象具備[[Constructor]]屬性以及原型,你就能夠對該表達式使用extends進行繼承。因爲extends後面可以接收任意類型的表達式,這就帶來了巨大的可能性,能夠動態決定基類,所以一種對象混入的方式:

//從表達式中派生類

let SerializableMixin = {
	serialize() {
		return JSON.stringify(this);
}
};
let AreaMixin = {
	getArea() {
		return this.length * this.width;
	}
};
function mixin(...mixins) {
	var base = function() {};
	Object.assign(base.prototype, ...mixins);
	return base;
} 
class Square extends mixin(AreaMixin, SerializableMixin) {
	constructor(length) {
	super();
	this.length = length;
	this.width = length;
	}
} 
let x = new Square(3);
console.log(x.getArea()); // 9
console.log(x.serialize()); // "{"length":3,"width":3}"
複製代碼

mixin()函數接受表明混入對象的任意數量的參數,它建立了一個名爲 base 的函數,並將每一個混入對象的屬性都賦值到新函數的原型上。此函數隨後被返回,因而 Square 就可以對其使用 extends 關鍵字了。注意因爲仍然使用了 extends ,你就必須在構造器內調用 super()。若多個混入對象擁有相同的屬性,則只有最後添加 的屬性會被保留。

4. 繼承內置對象

在E6中可以經過extends繼承JS中內置對象,例如:

class MyArray extends Array {
// 空代碼塊
} 
let colors = new MyArray();
colors[0] = "red";
console.log(colors.length); // 1
colors.length = 0;
console.log(colors[0]); // undefined
複製代碼

Symbol.species

屬性Symbol.species被用於定義靜態訪問器屬性,該屬性值用來指定類的構造器。當建立一個新的對象實例時,就須要經過Symbol.species屬性獲取到構造器,從而新建對象實例。

下面內置對象都定義了Symbol.species屬性:

  • Array;
  • ArrayBuffer;
  • Map;
  • Promise;
  • RegExp;
  • Set;
  • 類型化數組

例如在自定義類型中,使用Symbol.species:

class MyClass {
	static get [Symbol.species]() {
		return this;
	} 
	constructor(value) {
		this.value = value;
	} 
	clone() {
		return new this.constructor[Symbol.species](this.value);
	}
} 
class MyDerivedClass1 extends MyClass {
	// 空代碼塊
} 
class MyDerivedClass2 extends MyClass {
	static get [Symbol.species]() {
		return MyClass;
	}
} 
let instance1 = new MyDerivedClass1("foo"),
clone1 = instance1.clone(),
instance2 = new MyDerivedClass2("bar"),
clone2 = instance2.clone();
console.log(clone1 instanceof MyClass); // true
console.log(clone1 instanceof MyDerivedClass1); // true
console.log(clone2 instanceof MyClass); // true
console.log(clone2 instanceof MyDerivedClass2); // false
複製代碼

此處, MyDerivedClass1 繼承了 MyClass ,而且未修改 Symbol.species 屬性。因爲 this.constructor[Symbol.species] 會返回 MyDerivedClass1 ,當 clone() 被調用時,它就 返回了 MyDerivedClass1 的一個實例。 MyDerivedClass2 類也繼承了 MyClass ,但重寫了 Symbol.species,讓其返回 MyClass 。當 clone()MyDerivedClass2 的一個實例上被調 用時,返回值就變成 MyClass 的一個實例。使用 Symbol.species,任意派生類在調用應當 返回實例的方法時,均可以判斷出須要返回什麼類型的值。

在類構造器中使用new.target

使用new.target屬性可以判斷當前實例對象是由哪一個類構造器進行建立的,簡單的狀況下,new.target屬性就等於該類的構造器函數,同時new.target屬性也只能在構造器內被定義。

class Rec{
	constructor(){
		console.log(new.target===Rec);
	}
}

class Square extends Rec{

}

let rec = new Rec();
let square = new Square();

輸出:true false
複製代碼

當建立Rec對象實例時,new.target指代的是Rec自身的構造器,所以new.target===Rec會返回true,而Rec的派生類Squarenew.target會指向它自身的構造器,所以new.target===Rec會返回false;

可使用new.target來建立一個抽象基類:

class Rec{
	constructor(){
		if(new.target===Rec){
			throw new Error('abstract class');
		}
	}
}

class Square extends Rec{

}

let rec = new Rec(); //Uncaught Error: abstract class
let square = new Square(); //不會報錯
複製代碼

當試圖建立一個Rec實例對象時,會拋出錯誤,所以Rec能夠當作一個抽象基類。

5. 總結

  1. ES6中的類使用關鍵字class進行定義,便可以採用類聲明的方式也能夠採用類表達式進行定義。 此外,類構造器被調用時不能缺乏 new ,確保了不能意外地將類做爲函數來調用用。

  2. 基於類的繼承容許你從另外一個類、函數或表達式上派生新的類。這種能力意味着你能夠調用一個函數來判斷須要繼承的正確基類,也容許你使用混入或其餘不一樣的組合模式來建立一個新類。新的繼承方式讓繼承內置對象(例如數組) 也變爲可能,而且其工做符合預期。

  3. 可使用new.target來判斷建立實例對象時所用的類構造器。利用new.target能夠用來建立一個抽象基類;

總之,類是 JS 的一項新特性,它提供了更簡潔的語法與更好的功能,經過安全一致的方式來自定義一個對象類型。

相關文章
相關標籤/搜索