1. 程式人生 > >ES5-ES6-ES7_class類

ES5-ES6-ES7_class類

函數返回 面向 做到 prope 需求 子類 ted _屬性 class關鍵字

傳統創建對象模板的方式

JavaScript 語言中,生成實例對象的傳統方法是通過構造函數

//JavaScript 語言中,生成實例對象的傳統方法是通過構造函數

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

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

var p = new Point(1, 2);
console.log(p.toString());  
//(1, 2)

ES6創建對象模板的方式Class

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

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

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

//ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類
class Point {
    constructor(x, y) {   
//構造方法,而this關鍵字則代表實例對象 this.x = x; this.y = y; } //toString方法。註意,定義“類”的方法的時候,前面不需要加上function這個關鍵字,直接把函數定義放進去了就可以了。 // 另外,方法之間不需要逗號分隔,加了會報錯 toString() { return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)‘; } } const p = new Point(1,2); //類必須使用new調用,否則會報錯。這是它跟普通構造函數的一個主要區別,後者不用new也可以執行
console.log(p.toString()); //(1, 2) console.log(typeof Point) //function,類的數據類型就是函數,類本身就指向構造函數 console.log(Point === Point.prototype.constructor) // true

constructor 方法

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

//定義了一個空的類Point,JavaScript 引擎會自動為它添加一個空的constructor方法
class Point {
}

// 等同於
class Point {
    constructor() {}
}
class Foo {
    constructor() {   //constructor方法默認返回實例對象(即this),完全可以指定返回另外一個對象
        return Object.create(null);   //constructor函數返回一個全新的對象,結果導致實例對象不是Foo類的實例
    }
}

console.log(new Foo() instanceof Foo);  // false

類的實例對象

根據類創建實例對象——類必須使用new調用,否則會報錯。這是它跟普通構造函數的一個主要區別,後者不用new也可以執行

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

const p = new Point(1,2);   //類必須使用new調用,否則會報錯。這是它跟普通構造函數的一個主要區別,後者不用new也可以執行
console.log(p.toString());  //(1, 2)

實例對象屬性與原型對象屬性——實例的屬性除非顯式定義在其本身(即定義在this對象上),否則都是定義在原型上(即定義在class上)

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

const p = new Point(1,2);
console.log(p.toString());  //(1, 2)

//x和y都是實例對象point自身的屬性(因為定義在this變量上)不是原型對象的,所以hasOwnProperty方法返回true
console.log(Point.hasOwnProperty(‘x‘)) // true
console.log(Point.hasOwnProperty(‘y‘))// true
console.log(Point.__proto__.hasOwnProperty(‘y‘))// false

//toString是原型對象的屬性(因為定義在Point類上),不是實例對象的,所以hasOwnProperty方法返回false
console.log(Point.hasOwnProperty(‘toString‘)) // false
console.log(Point.__proto__.hasOwnProperty(‘toString‘)) // true

通過__proto__屬性為‘類’添加方法

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)‘;
    }
}
var p1 = new Point(2,3);
var p2 = new Point(3,2);

//p1和p2都是Point的實例,它們的原型都是Point.prototype,所以__proto__屬性是相等的
console.log(p1.__proto__ === p2.__proto__) //true

//可以通過實例的__proto__屬性為“類”添加方法
p1.__proto__.printName = function () {
    return ‘Oops‘
};
console.log(p1.printName()) // "Oops"
console.log(p2.printName()) // "Oops"
var p3 = new Point(4,2);
console.log(p3.printName()) // "Oops"

//p1的原型上添加了一個printName方法,由於p1的原型就是p2的原型,因此p2也可以調用這個方法。
// 而且,此後新建的實例p3也可以調用這個方法。
// 這意味著,使用實例的__proto__屬性改寫原型,必須相當謹慎,不推薦使用,因為這會改變“類”的原始定義,影響到所有實例

立即執行的類的實例

let person = new class {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}(‘張三‘);

person.sayName(); // "張三"

使用表達式的形式定義類

const MyClass = class Me {
    getClassName() {
        return Me.name;
    }
};

//使用表達式定義了一個類。需要註意的是,這個類的名字是MyClass而不是Me,Me只在 Class 的內部代碼可用,指代當前類

const p = new MyClass();
console.log(p.getClassName()); //Me,Me只在 Class 內部有定義。

變量提升——類不存在變量提升(hoist), ES6 不會把類的聲明提升到代碼頭部。這種規定的原因與下文要提到的繼承有關,必須保證子類在父類之後定義

//Foo類使用在前,定義在後,這樣會報錯,
// 因為 ES6 不會把類的聲明提升到代碼頭部。這種規定的原因與下文要提到的繼承有關,必須保證子類在父類之後定
new Foo(); // ReferenceError: Foo is not defined
class Foo {}

私有方法

私有方法是常見需求,但 ES6 不提供,只能通過變通方法模擬實現

私有方法模擬實現方式一——在命名上加以區別,方法前面的下劃線,表示這是一個只限於內部使用的私有方法。但是,這種命名是不保險的,在類的外部,還是可以調用到這個方法

class Widget {
    foo (baz) {     // 公有方法
        this._bar(baz);
    }
    _bar(baz) {    // 私有方法,_bar方法前面的下劃線,表示這是一個只限於內部使用的私有方法
        return this.snaf = baz;
    }
}

const w = new Widget();
console.log(w._bar(‘fffffff‘)) //fffffff,這種命名是不保險的,在類的外部,還是可以調用到這個方法

私有方法模擬實現方式二————將私有方法移出模塊,因為模塊內部的所有方法都是對外可見的

class Widget {
    foo (baz) {
        bar.call(this, baz);
    }
}

function bar(baz) {
    return this.snaf = baz;
}
//foo是公有方法,內部調用了bar.call(this, baz)。這使得bar實際上成為了當前模塊的私有方法
const w = new Widget();
console.log(w.foo(‘fffff‘))

Class的繼承

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

子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承父類的this對象,然後對其進行加工。如果不調用super方法,子類就得不到this對象

ES6 的繼承機制實質是先創造父類的實例對象this(所以必須先調用super方法),然後再用子類的構造函數修改this

如果子類沒有定義constructor方法,這個方法會被默認添加,也就是說,不管有沒有顯式定義,任何一個子類都有constructor方法。

另一個需要註意的地方是,在子類的構造函數中,只有調用super之後,才可以使用this關鍵字,否則會報錯。這是因為子類實例的構建,是基於對父類實例加工,只有super方法才能返回父類實例

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

class ColorPoint extends Point {
    constructor(x, y, color) {

        //this.color = color; //在調用super之前,this是不存在的,所以會報錯
        super(x, y); // 調用父類的constructor(x, y),
        this.color = color;
    }

    toString() {
        return this.color + ‘ ‘ + super.toString(); // 調用父類的toString()
    }
}

const cp = new ColorPoint(1,2,‘red‘);
console.log(Object.getPrototypeOf(ColorPoint)); //[Function: Point]
console.log(Object.getPrototypeOf(ColorPoint) === Point);  //true

通過繼承創建的實例對象所屬的類的實例——實例對象cp同時是ColorPoint和Point兩個類的實例

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

class ColorPoint extends Point {
    constructor(x, y, color) {

        //this.color = color; //在調用super之前,this是不存在的,所以會報錯
        super(x, y); // 調用父類的constructor(x, y),
        this.color = color;
    }

    toString() {
        return this.color + ‘ ‘ + super.toString(); // 調用父類的toString()
    }
}

const cp = new ColorPoint(1,2,‘red‘);
console.log(cp instanceof Point); // true
console.log(cp instanceof ColorPoint); // true

Object.getPrototypeOf()——Object.getPrototypeOf方法可以用來從子類上獲取父類,可以使用這個方法判斷,一個類是否繼承了另一個類

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

class ColorPoint extends Point {
    constructor(x, y, color) {

        //this.color = color; //在調用super之前,this是不存在的,所以會報錯
        super(x, y); // 調用父類的constructor(x, y),
        this.color = color;
    }

    toString() {
        return this.color + ‘ ‘ + super.toString(); // 調用父類的toString()
    }
}

const cp = new ColorPoint(1,2,‘red‘);
console.log(Object.getPrototypeOf(ColorPoint)); //[Function: Point]
console.log(Object.getPrototypeOf(ColorPoint) === Point);  //true

super關鍵字

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

super雖然代表了父類的構造函數,但是返回的是子類的實例,即super內部的this指的是子類,因此super()在這裏相當於父類.prototype.constructor.call(this)。

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        console.log(new.target.name);
    }
    toString() {
        return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)‘;
    }
}

class ColorPoint extends Point {
    constructor(x, y, color) {
        //子類的構造函數之中的super(),代表調用父類的構造函數。這是必須的,否則 JavaScript 引擎會報錯
        super(x, y); // 調用父類的constructor(x, y),
        this.color = color;
    }

    toString() {
        //super();   //SyntaxError: ‘super‘ keyword unexpected here
     // 作為函數時,super()只能用在子類的構造函數之中,用在其他地方就會報錯
return this.color + ‘ ‘ + super.toString(); // 調用父類的toString() } } const cp = new ColorPoint(1,2,‘red‘); //ColorPoint,創建子類對象的時候,this指向的是子類 console.log(cp.toString()) // red (1, 2)

super作為對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類

技術分享圖片

由於super指向父類的原型對象,所以定義在父類實例上的方法或屬性,是無法通過super調用的

如果屬性定義在父類的原型對象上,super就可以取到

技術分享圖片

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

class Point {
    constructor() {
        this.x = 1;
    }
    toString() {
        return ‘(‘ + this.x + ‘)‘;
    }
}
Point.prototype.z = 200;

class ColorPoint extends Point {
    constructor() {
        super();
        this.x = 3
    }

    toString() {
        // 調用父類的toString(),通過super調用父類的方法時,super會綁定子類的this
        return super.toString();
    }
}
const cp = new ColorPoint();
console.log(cp.toString()) //(3)

ES5-ES6-ES7_class類