天天看點

【ES6】08-面向對象Class類和繼承

對象

在javascript中,對象是一組無序的相關屬性和方法的集合,所有的事物都是對象,例如字元串,數值,數組,函數等。

對象是由屬性和方法組成的:

  • 屬性:事物的特征,在對象中用屬性來表示(常用名詞)
  • 方法:事物的行為,在對象中用方法來表示(常用動詞)

類 class

在ES6中新增了類的概念,可以使用​

​class​

​關鍵字來聲明一個類,之後使用這個類來執行個體化對象

類抽象了對象的公共部分,它泛指某一大類

對象特指某一個,通過類執行個體化一個具體的對象

class的基本文法

1.簡介

JavaScript語言中,生成執行個體對象的傳統方法是通過構造函數。

function Point(x,){
    this.x = x
    this.y = y
}
Point.prototype.toString = function() {
    return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);      

ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念,作為對象的模闆。通過​

​class​

​關鍵字,可以定義類。

基本上,ES6的​

​class​

​​可以看作是一個文法糖,它的絕大部分功能,ES5都可以做到,新的​

​class​

​寫法隻是讓對象原型的寫法更加清晰,更像面向對象便形成的文法而已。

class Point {
    constructor(x,){
        this.x = x
        this.y = y
    }
    toString() {
       return '(' + this.x + ', ' + this.y + ')';
    }
}      

ES6的類,完全可以看做構造函數的另一種寫法

class Point {
    //...
}
typeof Point  //'function'
Point === Point.prototype.constructor      

上面代碼表明,類的資料類型就是函數,類本身就是指向構造函數

使用的時候也是直接使用​​

​new​

​指令,跟構造函數的用法完全一緻。

class Bar {
    doStuff() {
        console.log('stuff')
    }
}
var b = new Bar()
b.doStuff()   //stuff      

構造函數的​

​prototype​

​​屬性,在ES6的類上面繼續存在。事實上,類的所有方法都定義在類的​

​prototype​

​屬性上面。在類的執行個體上面調用方法,其實就是調用原型上的方法。

clas B {}
let b = new B
b.constructor === B.prototype.constructor      

上面代碼中,​

​b​

​​是​

​B​

​​類的執行個體,它的​

​constructor​

​​方法就是​

​B​

​​類原型的​

​constructor​

​方法

由于類的方法都定義在​

​prototype​

​​對象上面,是以類的新方法可以添加在​

​prortotype​

​​對象上面。​

​Object.assign​

​方法可以很友善地一次性添加多個方法。

class Point{
    constructor(){
        //...
    }
}
Object.assign(Point.prototype,{
    toString(){},
    toValue(){}
})      

​prototype​

​​對象的​

​constructor​

​屬性,直接指向類的本身,這與ES5的行為是一緻的。

Point.prototype.constructor === Point    //true      

另外,類的内部所有定義的方法,渎職不可枚舉的(non-enumerable)

class Point{
    constructor(x,){
        //...
    }
    toString() {
        //...
    }
}
Object.keys(Point.prototype)
//[]
Object.getOwnPropertyNames(Point.prototype)      

2. constructor方法

​constructor​

​​方法是類的預設方法,通過​

​new​

​​指令生成對象執行個體,自動調用該方法。

一個類必須有​​

​constructor​

​​方法,如果沒有顯示定義,一個空的​

​constructor​

​方法會被預設添加。

class Point {
    
}

//等同于
class Point {
    constructor(){
        
    }
}      

​constructor​

​​方法預設傳回執行個體對象(即​

​this​

​),完全可以指定傳回另外一個對象

類必須使用​

​new​

​​ 調用,否則會報錯。這是它跟普通函數的一個主要差別,後者不用​

​new​

​也可以執行。

3. 類的執行個體

生成類的執行個體的寫法,與ES5完全一緻,也是使用​

​new​

​​指令,但必須使用​

​new​

​調用,否則報錯。

執行個體的屬性除非顯示定義在其本身(即定義在​

​this​

​​對象上),否則都是定義在原型上(即定義在​

​class​

​上)。

//定義類
class Point {
    //構造器
    constructor(name,){
        //執行個體屬性,這裡的this指向Point的執行個體對象
        this.name = name
        this.age = age
    }
    //執行個體方法
    sayName() {
        console.log(this.name)
    }
    //靜态方法
    static say() {
        console.log('靜态方法')
    }
}
//靜态屬性
Point.staticAttr = '靜态屬性'

//建立執行個體
let point = new Point('zhangsan', 18)
//調用執行個體方法
point.sayName()    //'zhangsan'

//通路靜态屬性,靜态方法
console.log(Point.staticAttr)
Point.say()      

4. 靜态方法

類相當于執行個體的原型,所有在類中定義的方法,都會被執行個體繼承。如果在一個方法前,加上​

​static​

​關鍵字,就表示該方法不會被執行個體繼承,而是直接通過類來調用,這就稱為“靜态方法”。

class Point{
    //靜态方法,隻能通過類來調用,執行個體是不會繼承靜态方法的
    static say(){
        console.log('靜态方法')
    }
}      

5. 執行個體方法

類的執行個體方法可以直接定義在類中,不用static修飾的方法都會被類的執行個體繼承

class Point{
    //執行個體方法
    sayName(){
        console.log('執行個體方法')
    }
}      

6. 靜态屬性

直接在類上定義的屬性是類的靜态屬性,注意:并不是寫在class裡面,而是下面這種寫法:

class Point{
}
Point.staticName = 'lisi'      

7. 執行個體屬性

類的執行個體屬性可以定義在構造函數中(constructor)

class Point{
    constructor(id,name){
        this.id = id
        this.name = name
    }
}      

class的繼承

1. 簡介

class可以通過​

​extends​

​關鍵字實作繼承,這比ES5的通過修改原型鍊實作繼承,要清晰和友善很多。

class Point {
    constructor(x,y){
        this.x = x
        this.y = y
    }
    toString(){
        
    }
}

class ColorPoint extends Point {
    constructor(x, y,){
        super(x, y)   //調用父類的constructor(x,y)
        this.color = color
    }
    toString() {
        return this.color + ' ' + super.toString()   //調用父類的toString()
    }
}      

上面代碼定義了一個​

​ColorPoint​

​​類,該類通過​

​extends​

​​關鍵字,繼承了​

​Point​

​類的所有屬性和方法(包括靜态的和執行個體的)

子類必須在​

​constructor​

​​方法中調用​

​super​

​​方法,否則建立執行個體會報錯。這是因為子類自己的​

​this​

​​對象,必須先通過父類的構造函數完成塑造,得到與弗雷同樣的執行個體屬性和方法,然後再對其經行加工,加上子類自己的執行個體屬性和方法。如果不調用​

​super​

​​方法,子類就得不到​

​this​

​對象。

2. Object.getPrototypeOf()

​Object.getPrototypeOf​

​方法可以用來從子類上擷取父類

Object.getPrototypeOf(ColorPoint) ===      

是以,可以用這個方法判斷一個類是否繼承了另一個類。

3. super關鍵字

​super​

​這個關鍵字,既可以當做函數調用,也可以當做對象使用。在這兩種情況下,它的用法完全不同。

  • 第一種情況,​

    ​super​

    ​​作為函數調用時,代表父類的構造函數。ES6要求,子類的構造函數必須執行一次​

    ​super​

    ​函數。
class A {}
  class B extends A {
      constructor() {
          super()
      }
  }      

上面代碼中,子類​

​B​

​​的構造函數之中的​

​super()​

​,代表調用父類的構造函數。這是必須的,否則報錯。

注意:​

​super​

​​雖然代表了父類​

​A​

​​的構造函數,但是傳回的是子類​

​B​

​​的執行個體,即​

​super​

​​内部的​

​this​

​​指的是​

​B​

​​的執行個體,是以​

​super​

​​在這裡相當于​

​A.prototype.constructor.call(this)​

作為函數時,​

​super()​

​隻能用在子類的構造函數之中,用在其他地方就會報錯。

  • 第二種情況,​

    ​super​

    ​作為對象時,在普通方法中,指向父類的原型對象;在靜态方法中,指向父類。
class A {
    p(){
        return 2
    }
}

class B extends A {
    constructor() {
        super()
        console.log(super.p())   //2  這裡其實是調用了父類A的p方法
    }
}      

這裡需要注意,由于​

​super​

​​指向父類的原型對象,是以定義在父類執行個體上的方法或屬性,是無法通過​

​super​

​調用的。

class A {
    constructor(){
        this.p = 2
    }
}

class B extends A {
    get m() {
        return super.p
    }
}
let b = new B()
b.m   //undefined      

4. 類的prototype屬性和__ptoto__屬性

大多數浏覽器的ES5實作之中,每一個對象都有​

​__proto__​

​​屬性,指向對應的構造函數的​

​prototype​

​​屬性。class作為構造函數的文法糖,同時有​

​prototype​

​​屬性和​

​__proto__​

​屬性,是以同時存在兩條繼承鍊

class A {}
class B extends A {}
B.__proto__ === A   //true
B.prototype.__proto__ === A.prototype   //true      
class A {}

class B {}

//B的執行個體繼承A的執行個體
Object.setPrototypeOf(B.prototype,A.prototype)

//B 繼承 A 的靜态屬性
Object.setPrototypeOf(B, A)

const b = new B()