es6學習筆記12--Class


Class基本語法

概述

JavaScript語言的傳統方法是通過構造函數,定義並生成新對象。下面是一個例子。

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

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

上面這種寫法跟傳統的面向對象語言(比如C++和Java)差異很大,很容易讓新學習這門語言的程序員感到困惑。

ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。上面的代碼用ES6的“類”改寫,就是下面這樣。

//定義類
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

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

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

構造函數的prototype屬性,在ES6的“類”上面繼續存在。事實上,類的所有方法都定義在類的prototype屬性上面

class Point {
  constructor(){
    // ...
  }

  toString(){
    // ...
  }

  toValue(){
    // ...
  }
}

// 等同於

Point.prototype = {
  toString(){},
  toValue(){}
};

在類的實例上面調用方法,其實就是調用原型上的方法。

class B {}
let b = new B();

b.constructor === B.prototype.constructor // true

類的內部所有定義的方法,都是不可枚舉的(non-enumerable)。

constructor方法

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

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

class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo
// false

上面代碼中,constructor函數返回一個全新的對象,結果導致實例對象不是Foo類的實例。

類的實例對象

生成類的實例對象的寫法,與ES5完全一樣,也是使用new命令。如果忘記加上new,像函數那樣調用Class,將會報錯。

// 報錯
var point = Point(2, 3);

// 正確
var point = new Point(2, 3);

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

與ES5一樣,類的所有實例共享一個原型對象。

這也意味着,可以通過實例的__proto__屬性為Class添加方法。

var p1 = new Point(2,3);
var p2 = new Point(3,2);

p1.__proto__.printName = function () { return 'Oops' };

p1.printName() // "Oops"
p2.printName() // "Oops"

var p3 = new Point(4,2);
p3.printName() // "Oops"

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

name屬性

由於本質上,ES6的Class只是ES5的構造函數的一層包裝,所以函數的許多特性都被Class繼承,包括name屬性。

class Point {}
Point.name // "Point"

name屬性總是返回緊跟在class關鍵字后面的類名。

Class表達式

與函數一樣,Class也可以使用表達式的形式定義。

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

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

let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined

上面代碼表示,Me只在Class內部有定義。

如果Class內部沒用到的話,可以省略Me,也就是可以寫成下面的形式。

const MyClass = class { /* ... */ };

采用Class表達式,可以寫出立即執行的Class。

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}('張三');

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

上面代碼中,person是一個立即執行的Class的實例。

不存在變量提升

Class不存在變量提升(hoist),這一點與ES5完全不同。

new Foo(); // ReferenceError
class Foo {}

上面代碼中,Foo類使用在前,定義在后,這樣會報錯,因為ES6不會把變量聲明提升到代碼頭部。這種規定的原因與下文要提到的繼承有關,必須保證子類在父類之后定義。

Class的繼承

基本用法

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

class ColorPoint extends Point {}

上面代碼定義了一個ColorPoint類,該類通過extends關鍵字,繼承了Point類的所有屬性和方法。但是由於沒有部署任何代碼,所以這兩個類完全一樣,等於復制了一個Point類。下面,我們在ColorPoint內部加上代碼。

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

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

上面代碼中,constructor方法和toString方法之中,都出現了super關鍵字,它在這里表示父類的構造函數,用來新建父類的this對象。

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

class Point { /* ... */ }

class ColorPoint extends Point {
  constructor() {
  }
}

let cp = new ColorPoint(); // ReferenceError

上面代碼中,ColorPoint繼承了父類Point,但是它的構造函數沒有調用super方法,導致新建實例時報錯

類的prototype屬性和__proto__屬性

大多數瀏覽器的ES5實現之中,每一個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class作為構造函數的語法糖,同時有prototype屬性和__proto__屬性,因此同時存在兩條繼承鏈。

(1)子類的__proto__屬性,表示構造函數的繼承,總是指向父類。

(2)子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。

class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

上面代碼中,子類B__proto__屬性指向父類A,子類Bprototype屬性的__proto__屬性指向父類Aprototype屬性。

這樣的結果是因為,類的繼承是按照下面的模式實現的。

class A {
}

class B {
}

// B的實例繼承A的實例
Object.setPrototypeOf(B.prototype, A.prototype);

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

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用來從子類上獲取父類。

Object.getPrototypeOf(ColorPoint) === Point
// true

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

super關鍵字

super這個關鍵字,有兩種用法,含義不同。

(1)作為函數調用時(即super(...args)),super代表父類的構造函數。

(2)作為對象調用時(即super.propsuper.method()),super代表父類。注意,此時super即可以引用父類實例的屬性和方法,也可以引用父類的靜態方法。

 

原生構造函數的繼承

原生構造函數是指語言內置的構造函數,通常用來生成數據結構。ECMAScript的原生構造函數大致有下面這些。

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

以前,這些原生構造函數是無法繼承的,ES6允許繼承原生構造函數定義子類,因為ES6是先新建父類的實例對象this,然后再用子類的構造函數修飾this,使得父類的所有行為都可以繼承。下面是一個繼承Array的例子。

class MyArray extends Array {
  constructor(...args) {
    super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined

上面代碼定義了一個MyArray類,繼承了Array構造函數,因此就可以從MyArray生成數組的實例。這意味着,ES6可以自定義原生數據結構(比如Array、String等)的子類,這是ES5無法做到的。

上面這個例子也說明,extends關鍵字不僅可以用來繼承類,還可以用來繼承原生的構造函數。因此可以在原生數據結構的基礎上,定義自己的數據結構。

Class的靜態方法

類相當於實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承而是直接通過類來調用,這就稱為“靜態方法”。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: undefined is not a function

上面代碼中,Foo類的classMethod方法前有static關鍵字,表明該方法是一個靜態方法,可以直接在Foo類上調用(Foo.classMethod()),而不是在Foo類的實例上調用。如果在實例上調用靜態方法,會拋出一個錯誤,表示不存在該方法。

父類的靜態方法,可以被子類繼承

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod(); // 'hello'

上面代碼中,父類Foo有一個靜態方法,子類Bar可以調用這個方法。

靜態方法也是可以從super對象上調用的。

new.target屬性

new是從構造函數生成實例的命令。ES6為new命令引入了一個new.target屬性,(在構造函數中)返回new命令作用於的那個構造函數。如果構造函數不是通過new命令調用的,new.target會返回undefined,因此這個屬性可以用來確定構造函數是怎么調用的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM