ECMAScript 6 類和類的繼承

類和類的繼承

一、類的聲明

JS中到處都是對象,它是一門基於對象的語言,JS代碼的執行都依託於對象,利用對象產生各種具備關聯性的上下文環境,JS代碼才能正常運行。

但是我們都知道在ES5中,沒有class,我們只是利用函數、new關鍵詞、原型等方式實現類的概念和基本使用,但是隨着技術的發展和編程語言的互相靠攏,只使用構造函數或者工廠模式去實現類的封裝調用很不現實,代碼不明確,容易讓學習者和開發者產生誤會。

所以 ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作爲對象的模板。通過class關鍵字,可以定義類。

基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。

ES5聲明類

function Point(x, y) {
  this.x = x;
  this.y = y;
}

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

let p = new Point(3, 4);

ES6聲明類

class Point {
    // 構造函數
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    // 原型函數
    toString = function () {
        return '(' + this.x + ', ' + this.y + ')';
    };
}

上面代碼定義了一個“類”,可以看到裏面有一個constructor方法,這就是構造方法,而this關鍵字則代表實例對象。也就是說,ES5 的構造函數Point,對應 ES6 的Point類的構造方法。

Point類除了構造方法,還定義了一個toString方法。注意,定義“類”的方法的時候,前面不需要加上function這個關鍵字,直接把函數定義放進去了就可以了。另外,方法之間不需要逗號分隔,加了會報錯。

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

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

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

注:

  • 依舊用new關鍵詞從類裏實例化對象;
  • 類必須使用new調用,否則會報錯(ES5的構造函數可以被當做一個函數調用);

由於類的方法都定義在prototype對象上面,所以類的新方法可以添加在prototype對象上面。Object.assign方法可以很方便地一次向類添加多個方法。

class Point {

}

// 有時候根據代碼邏輯的需求,我們在聲明瞭類之後的某一處可能要爲這個類添加若干個原型函數,如果使用 類名.prototype的方式添加,代碼冗餘會很高,所以可以使用Object.assign的方式
Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

二、類的constructor函數

constructor方法是類的默認方法,通過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認添加。

constructor方法默認返回實例對象(即this),也可以指定返回另外一個對象。

class Point {
    constructor() {
       return {
           name: 'tom'
       };     
    }
}
let p = new Point(); // {name: 'tom'}

注:

  • 構造函數的prototype屬性,在 ES6 的“類”上面繼續存在;事實上,類中聲明的所有方法都定義在類的prototype屬性上面;
  • 類的prototype對象的constructor屬性,直接指向“類”的本身,這與 ES5 的行爲是一致的;

三、類的屬性和方法

class Point {
    legs = 2; // 實例屬性
    _mouth = 1; // 私有屬性
    static eyes = 2; // 靜態屬性   
    constructor(name, sex) {
        // 實例屬性
        this.name = name;
        this.sex = sex;
        // 實例方法
        this.saysomething = function() {
            console.log('saysomething');
        }

        this._getMoney(); // 調用私有方法
    }
    // 原型方法
    run() {
        console.log('run');
    }
    _getMoney () {
        console.log(this._mouth);  
    }
    // 靜態方法
    static eat() {
        console.log('eat');
    }
}

// 實例化一個對象
let p = new Point();
console.log(p.legs,p.name,p.sex); // 實例屬性
console.log(Point.eyes); // 靜態屬性
p.saysomething(); // 實例方法
p.run(); // 原型方法
Point.eat(); // 靜態方法

注:

  • 靜態屬性和靜態方法是類直接調用的屬性和方法,類的實例無法使用。
  • 私有方法和私有屬性,是隻能在類的內部訪問的方法和屬性,外部不能訪問。這是常見需求,有利於代碼的封裝,但 ES6 不提供,只能通過變通方法模擬實現。
  • 私有屬性和私有方法的

練習:封裝一個分數類,可以進行約分、加、減、乘、除運算。

四、取值函數(getter)和存值函數(setter)

有時候類的某些屬性需要進行特殊的處理之後才能被取值或者賦值。這個時候就要用到getter和setter函數。
如:

class People {
    a = 1000; // 私有屬性
    get money() {
        return this.a - 100;
    }
    set money(val) {
        this.a = val * 2;
    }
}

let p = new People();
// 取出money的值
console.log(p.money);
// 設置money的值
p.money = 3000;
console.log(p.money);

五、類的繼承

  1. Class 可以通過extends關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。
class Point {}

// ColorPoint通過extends繼承Point
class ColorPoint extends Point {}

如果子類繼承父類時,父類中有某些屬性需要實參來確定,那必須要在子類的constructor構造函數中,用super函數向父類傳參,否則瀏覽器會直接報錯。

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

// ColorPoint類繼承Point
class ColorPoint extends Point {
    constructor(color, x, y) {
        // 通過super函數向父類傳參
        super(x, y);
        // 綁定子類自己的屬性
        this.color = color;
    }
}

// 從ColorPoint類中實例化一個對象
let cp = new ColorPoint('#000', 100, 45);
console.log(cp);
  1. Object.getPrototypeOf方法可以用來從子類上獲取父類,可以用這個方法判斷一個類是否繼承了另一個類。
class Point {}
class ColorPoint extends Point {}
Object.getProtorypeOf(Point);
Object.getPrototypeOf(ColorPoint);
  1. super函數除了可以在子類中代表父類的構造函數之外,還可以作爲對象使用,當它作爲對象時,在類的普通函數中指父類的原型對象,在靜態方法中,指向父類。
class Point {
    // 實例屬性
    x = 0;
    y = 0;
    // 原型方法
    move() {
        this.x += 1;
        this.y += 1;
    }
    // 靜態方法
    static print() {
        console.log('父類的靜態方法');
    }
}

// ColorPoint繼承Point
class ColorPoint extends Point {
    color = '#000';
    change() {
        // 在原型方法中通過super調用父類的原型方法
        super.move();
        this.color = '#f00';
    }
    static printout() {
        // 在靜態方法中通過super調用父類的靜態方法
        super.print();
        console.log('子類的靜態方法');
    }
}

let cp = new ColorPoint(); // 創建實例
cp.change(); // 調用實例的change函數
console.log(cp);

ColorPoint.printout(); // 調用類的靜態方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章