es6之class

class與構造函數

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

//類的寫法
class Point{
    getName(){
        return 'lili';
    }
}

//構造函數的寫法
function Point2() {}
Point2.prototype.getName = function () {
    return 'lili';
}
複製代碼

其實class也是函數,類裏的方法也是定義在prototype上。類的方法都是不可枚舉的,經過Object.keys不能得到,可是構造函數原型上的方法能夠得到。bash

//類
typeof Point; //'function'
Point === Point.prototype.constructor  //true

Object.getOwnPropertyNames(Point.prototype) 
//[ 'constructor', 'getName' ]

Object.keys(Point.prototype)
//[]

//構造函數
Object.getOwnPropertyNames(Point2.prototype);//包括自身的,可枚舉和不可枚舉的
//[ 'constructor', 'getName' ]
Object.keys(Point2.prototype);
[ 'getName' ]
複製代碼

constructor方法

類中能夠顯示定義constructor方法,若是沒有定義,則隱式建立一個空的constructor方法。app

class point {}

//等同於 
class Point{
    constructor(){
        
    }
}
複製代碼

constructor方法,默認返回值是類的實例對象(this)。能夠自定義返回值 ,若是這樣作改變this值了,最好不要這樣作。函數

class Point{
    constructor(x, y){
        this.x = x;
        this.y = y;
        return {}; //不推薦用法
    }
}
let point = new Point(1, 2); //{}
複製代碼

類實例

注意:生成實例,類必須使用new關鍵字定義,不然報錯。構造函數不是必須使用newpost

class Point{}
let point = Point.call({}); //報錯


function Point2(x) { 
    this.x = x; 
    return this;
}
let point2 = Point2.call({}, 1);//ok {x: 1}
複製代碼

類的全部實例共享一個原型對象學習

class Point { 
    y = 10; //定義屬性方式
    constructor(x){
        this.x = x; //定義屬性方式
    }
    setZ(z){
        this.z = z; //定義屬性
    }
    getX(){
        return this.x;
    }
}
let point = new Point(1); 
let point2 = new Point(2); 

point.hasOwnProperty('x'); //true
point.hasOwnProperty('getX'); //false

point.__proto__ === point2.__proto__ === Point.prototype //true

Object.getOwnPropertyNames(Point.prototype) 
//[ 'constructor', 'getX' ]
複製代碼

this是實例對象,定義在this上的屬於實例的自身的屬性,如x,y,z。getX方法是定義在原型對象上的,全部的實例共享的。ui

靜態屬性和靜態方法

靜態屬性如今尚未this

靜態方法spa

  • 屬於類,不被實例繼承,直接經過類來調用。能夠被子類繼承
  • 內部的this,執行Point自己,而不是實例
  • 能夠和非靜態方法重名
class Point {
    static getX() {
        this.getY();
    }
    static getY(){
        console.log('y');
    }
    getY(){
        console.log('yy');
    }
}
let point = new Point();
Point.getX();
//'y'
複製代碼

私有屬性和私有方法

私有屬性和私有方法尚未實現。只能約定下劃線打頭方法爲私有的。或利用Symbol值的惟一性生成prototype

const bar = Symbol('bar');
class Point {
    [bar](){
        console.log('x')
    }
}

let point = new Point()
console.log(Reflect.ownKeys(Point.prototype));
//[ 'constructor', Symbol(bar) ]
複製代碼

通常狀況下外界取不到bar的值,因此成了私有方法。可是也不是絕對不行,Reflect.ownKeys()依然能夠拿到它們。

new.target

new是從構造函數生成實例對象的命令。ES6 爲new命令引入了一個new.target屬性,該屬性通常用在構造函數之中,返回new命令做用於的那個構造函數。若是構造函數不是經過new命令或Reflect.construct()調用的,new.target會返回undefined,所以這個屬性能夠用來肯定構造函數是怎麼調用的。

class Point {
    constructor() {
        console.log(new.target === Point); //true
    }
}
let point = new Point();

複製代碼

只能在構造函數constructor中使用new.target。其餘地方使用會報錯

function Point2(name) {
    if (new.target === Point2) {
        this.name = name;
    } else {
        throw new Error('必須使用 new 命令生成實例');
    }
}
let point3 = new Point2('sha');
let point2 = Point2.call({}, 'li'); //拋出異常
複製代碼

利用new.target,限定構造函數建立實例,只能使用new操做符。

利用這個特色,能夠寫出不能獨立使用、必須繼承後才能使用的類

class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error('本類不能實例化');
    }
  }
}

class Rectangle extends Shape {
  constructor(length, width) {
    super();
    // ...
  }
}

var x = new Shape();  // 報錯
var y = new Rectangle(3, 4);  // 正確
複製代碼

繼承

繼承就是子類繼承父類的屬性和方法。注意如下幾點:

  • 在子類的構造函數中,只有調用super以後,纔可使用this關鍵字,不然會報錯
  • 子類必須在constructor方法中調用super方法,不然新建實例時會報錯。
  • 父類的靜態方法,也會被子類繼承
class Point {
    static hello() { //靜態方法會被子類繼承
        console.log('hello world');
    }
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    getX(){
        return this.x;
    }
}

class ColorPoint extends Point{
    constructor(x, y, color){
        this.color = color; //錯誤
        super(x, y);
        this.color = color; //正確
    }
    getX(){
        console.log(this); //ColorPoint { x: 10, y: 1, color: 'red' }
        return this.color + ' ' + super.getX();
    }
}

let cp = new ColorPoint(10, 1, 'red');
console.log(cp.getX()); //red 10
ColorPoint.hello(); //hello world

Object.getPrototypeOf(ColorPoint) === Point //true
cp instanceof Point //true
cp instanceof ColorPoint //true
複製代碼

ES5 的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制徹底不一樣,實質是先將父類實例對象的屬性和方法,加到this上面(因此必須先調用super方法),而後再用子類的構造函數修改this。若是不調用super方法,子類就得不到this對象。

super關鍵字

super這個關鍵字,既能夠看成函數使用,也能夠看成對象使用。

  • 第一種狀況,super做爲函數調用時,表明父類的構造函數。ES6 要求,子類的構造函數必須執行一次super函數。而且super函數只能用在子類的構造函數之中,用在其餘地方就會報錯。

  • 第二種狀況,super做爲對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。

    在子類普通方法中經過super調用父類的方法時,方法內部的this指向當前的子類實例。因爲super指向父類的原型對象,因此定義在父類實例上的方法或屬性,是沒法經過super調用的。

    在子類的靜態方法中經過super調用父類的方法時,方法內部的this指向當前的子類,而不是子類的實例。

class A {
  constructor() {
    console.log(new.target.name);
    this.x = 10;
  }
  getX(){
      return this.x;
  }
}
class B extends A {
  constructor() {
    super();
    this.x = 20;
  }
}
let a = new A() // A
let b = new B() // B

b.getX(); //20  
複製代碼

B中super()在這裏至關於A.prototype.constructor.call(this)。new.target指向當前正在執行的函數

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1  靜態方法

var child = new Child();
child.myMethod(2); // instance 2  普通方法
複製代碼

類的 prototype 屬性和__proto__屬性

  • 子類的__proto__屬性,表示構造函數的繼承,老是指向父類。

  • 子類prototype屬性的__proto__屬性,表示方法的繼承,老是指向父類的prototype屬性。

class A {
}

class B extends A {
}

B.__proto__ === A // true  屬性繼承
B.prototype.__proto__ === A.prototype // true 方法繼承
複製代碼

注:本文是讀阮一峯老師《ECMAScript 6 入門》的學習筆記

相關文章
相關標籤/搜索