全面瞭解ES6中的Class

寫在前面

在ES5及以前的版本中,並無類的概念,那時候建立類的方法是你們都很熟悉,以下是一個最簡單的例子:java

// 通常咱們約定以大寫字母開頭來表示一個構造函數
function Person (name) {
    this.name = name;
}
// 在構造函數的原型鏈上定義不一樣的方法
Person.prototype.getName = function () {
    return this.name;
}
// 實例化一個對象,擁有構造函數原型鏈上的方法
var people = new Person('lilei');
people.getName(); // lilei
複製代碼

可是這種寫法和傳統的面嚮對象語言(例如java)有較大區別,不太好理解。因此在ES6中引入了class關鍵字,它是一個讓對象原型的寫法更像面向對象編程語法的一個語法糖。node

在node開發以及一些組件的開發中,會常常用到class語法,本文將先介紹class的一些基礎用法,而後深刻剖析class的繼承,以及原型鏈相關的知識。總結一下本身學習class的過程,也但願經過一些示例能幫助到部分同窗,由淺及深,逐步理解class的用法以及原理。es6

基礎用法

寫法示例

咱們先來看一個class的常規寫法:編程

class Person {
    _myName = 'initial-name';
    constructor (name = '') {
        this._myName = name;
    }
    getName () {
        return this._myName;
    }
    static toUpperCaseName () {
        return this.name.toUpperCase();
    }
    get name () {
        return this._myName;
    }
    set name (name) {
        this._myName = name;
    }
}
let people = new Person('lilei');
console.log(people.name); // lilei
console.log(people.getName()); // lilei
console.log(Person.toUpperCaseName()); // PERSON
people.name = 'lilei2';
console.log(people.name); // lilei2
複製代碼

下面咱們把這個示例拆分開來,逐一瞭解class的基礎用法。bash

構造方法(constructor)

一、class在定義時,必須定義一個構造方法(constructor),若是不寫,Javascript引擎會自動添加一個空的constructor方法。函數

class Person {}

// 實際上是
class Person {
    constructor () {} 
}
複製代碼

二、constructor方法是class的構造方法,在class實例化對象時(即new一個對象時)會自動調用構造方法,而且默認返回實例對象(this),固然你徹底能夠返回另一個對象。學習

class Person {
    constructor (name) {
        console.log(name);
    } 
}
new Person('lilei'); // lilei
複製代碼

靜態方法

一、定義class的非靜態方法不用加上function關鍵字,也不須要用逗號隔開,用了反而會報錯。而這些普通方法在實例化時,就會被實例繼承。 二、定義class的靜態方法,則是在方法名前加static關鍵字,這樣定義的方法,就不會被實例繼承,可是它能夠直接被類調用,以下:ui

class Person {
    func1 () {
        console.log('func1');
    }
    static func2 () {
        console.log('func2');
    }
}
let people = new Person();
people.func1(); // func1
people.func2(); // people.func2 is not a function
Person.func1(); // Person.func1 is not a function
Person.func2(); // func2
複製代碼

三、靜態方法能夠與非靜態方法重名;靜態方法中,this指向的是類,不是實例。this

class Person {
  static func1() {
    console.log(this.prototype.func2());
    console.log(this.func2());
  }
  static func2() {
    console.log('static');
  }
  func2() {
    console.log('function');
  }
}
Person.func1();
// function
// static
複製代碼

四、非靜態方法中,不能直接使用this關鍵字來訪問靜態方法,而是要用類名來調用。若是靜態方法包含this關鍵字,這個this指的是類,而不是實例。spa

class Person {
    constructor() {
        console.log(Person.func1());
        console.log(this.constructor.func1());
    }
    static func1() {
        console.log('static');
    }
}
複製代碼

五、另外提一點,class的方法名能夠用變量來命名,以下:

let funcName = 'getName';
class Person {
    [funcName] () {}
}
複製代碼

六、類的全部方法都定義在類的prototype屬性上面

屬性的定義方法

一、在ES6中,屬性定義的常規寫法是定義在類的constructor方法裏的this上,示例見本文基礎用法中第一個代碼段,這裏就再也不寫了。 二、在ES7中定義了一種新的屬性定義規範,能夠將屬性定義在類的最頂層,這樣使代碼看上去更加整潔,很清晰的看到類中定義的屬性。

class Person {
    _name = '';
    constructor (name) {
        this._name = name;
    }
}
複製代碼

get與set

一、在class裏還能夠給屬性添加get和set關鍵字,攔截屬性的存取行爲。寫法見基礎用法中第一個代碼段。 二、當一個屬性只有getter沒有setter的時候,咱們是沒法進行賦值操做的,第一次初始化也不行。若是把變量定義在類的外面,就能夠只使用getter不使用setter。

let data = {};
class Person{
    constructor() {
        this.name = 'lilei';
        this.age = 20;
    }
    get age(){
        return this._age;  
    }
    // 若是沒有這個set,則會報錯:Cannot set property width of #<GetSet> which has only a getter
    set age(age){
        this._age = age;
    }
    get data(){
        return data;
    }
}  

let people = new Person();
console.log(people.age);
people.age = 30;
console.log(people.age);
console.log(people._age);
console.log(people.data);
複製代碼

三、不能在set方法中設置本身的值,否則會陷入無限遞歸,致使棧溢出。

set age (v) {
    this.age = v;
}
 // Uncaught RangeError: Maximum call stack size exceeded
複製代碼

class表達式

一、類能夠用表達式的方式來定義,須要注意的是P只能在class內部使用,用於指代當前的類;Person只能在外部使用。

const Person = class P {
    getName () {
        return P.name // p
    }
}
Person.name // P
複製代碼

二、和let同樣,class不存在變量提高,即不能先使用後聲明。 三、class擁有name屬性,返回的是class關鍵字後面的名字,見上面的例子。

深刻了解

屬性及方法定義的位置

實例的屬性及方法除非顯式定義在其自己(即定義在this對象上),不然都是定義在原型上(即定義在class上)。ES7中約定的新的定義屬性的規範也是定義在實例上的。

class P {
    a = 1;
    constructor(b) {
        this.b = b;
    }
    c() {
        console.log(this.a, this.b);
    }
}

var p1 = new P(2);

p1.hasOwnProperty('a') // true
p1.hasOwnProperty('b') // true
p1.hasOwnProperty('c') // false
p1.__proto__.hasOwnProperty('c') // true
複製代碼

class語法存在屏蔽方法的問題

咱們來看這樣一段代碼:

class P {
    constructor(id) {
        this.id = id;
    }
    id() {
        console.log( "Id: " + this.id );
    }
}
var p1 = new P( "p1" );
p1.id(); // TypeError -- p1.id 如今是字符串"p1"
複製代碼

在構造函數中定義的屬性會屏蔽同名的方法。注意這裏是屏蔽,不是覆蓋、前面說到經過this定義的屬性是定義在實例上的,而方法是定義在類上的,因此實例在讀取id的時候會率先讀取到定義在實例上的屬性id,也就讀不到方法id了。

繼承

class引入目的就是讓類定義起來更直觀接單,經過extends關鍵字實現繼承的寫法也更便於理解。其中class A extends B 中A稱爲派生類,派生類是指繼承自其它類的新類。

super

一、派生類沒有本身的this對象,而是繼承父類的this對象,因此須要在構造函數中先調用super()初始化this對象,以後才能在構造函數中訪問this。ES6 要求,子類的構造函數必須執行一次super函數。 二、super既能夠當函數使用,也能夠當對象使用。 1)當函數使用時,它表明的是父類的構造函數,而且只能在子類的構造函數中使用,否則就會報錯。 2)當對象使用時,若是是在普通方法中,指向對象的原型對象;若是是在靜態方法中,指向的是父類。

class Person {
    static getMethod() {
        console.log('static');
    }
    getMethod() {
        console.log('instance');
    }
}
class Man extends Person {
    static getMethod() {
        super.getMethod();
    }
    getMethod(msg) {
        super.getMethod();
    }
}
Man.getMethod(); // static
var lilei = new Man();
lilei.getMethod(); // instance
複製代碼

三、super指向的是父類的原型對象,因此定義在父類實例上的方法或屬性,是沒法經過super調用的。

class A {
	constructor() {
		this.p = 2;
	}
}
class B extends A {
	get m() {
		return super.p;
	}
}
let b = new B();
b.m; // undefined
複製代碼

四、經過super調用父類的方法時,super會綁定子類的this

class A {
	constructor() {
		this.color = 'red';
	}
	draw() {
		console.log(this.color);
	}
}
class B extends A {
	constructor() {
		super();
		this.color = 'yellow';
	}
	draw() {
		super.draw(); // 這裏是會綁定B的this,也就是super.draw.call(this)
	}
}
let b = new B();
b.draw() // yellow
複製代碼

派生自表達式的類

由於class繼承的邏輯是先新建一個父類的this對象,而後在子類的構造函數中對this進行加工,因此父類的全部行爲均可以被繼承。也就是class能夠繼承原生構造函數來定義子類

class MyArray extends Array {}
let arr = new MyArray(1,2);
console.log(arr); // [1, 2]
複製代碼

參考

ECMAScript 6 入門

相關文章
相關標籤/搜索