「總結」Class的基本語法

小提群Z 2019-06-18
「總結」Class的基本語法


一、Class簡介

1.1.類的由來:

相比於ES5中通過構造函數生成實例對象,ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。

class Bar {
doStuff() {
console.log('stuff');
}
}
var b = new Bar(); //用new來創建實例
b.doStuff() // "stuff"

在類的實例上調用方法,就等同於調用原型上的方法。

class B {}
let b = new B();
b.constructor === B.prototype.constructor // true

Object.assign方法可以很方便地一次向類添加多個方法。類內部定義的方法是不可枚舉的。

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

補充一點:類的內部所有定義的方法,都是不可枚舉的。

1.2.constructor 方法:

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

定義constructor方法,系統會默認定義。

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

類必須使用用new調用,否則會報錯。

1.3.類的實例:

生成實例用new,實例的屬性除非顯式定義在this對象上,不然都是定義在類的原型上;

  • 顯式定義的屬性用point.hasOwnProperty('x')判斷,存在返回true,否則false;
  • 定義在原型上的屬性用point.__proto__.hasOwnProperty('toString') 判斷;


1.4.取值函數(getter)和存值函數(setter)

在“類”的內部可以使用get和set關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行為。

class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'

1.5.屬性表達式

let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ... [methodName]為屬性表達式
}
}

1.6.Class 表達式

類的定義,除了用class,還可以用如下的Class表達式。

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

1.7.注意點:

(1)嚴格模式:

類和模塊默認為嚴格模式,沒必要聲明。

(2)變量提升:

類聲明時(class Foo()),不存在變量提升。

(3)類的name屬性:

name屬性總是返回緊跟在class關鍵字後面的類名,跟函數的name屬性是一致的。

(4)generator方法:

如果某個方法之前加上星號(*),就表示該方法是一個 Generator 函數。

(5)this的指向:

類的方法內部如果含有this,它默認指向類的實例。


二、靜態方法

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

如果靜態方法包含this關鍵字,這個this指的是類,而不是實例。

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


三、實例屬性的新寫法

class IncreasingCounter {
constructor() {
this._count = 0;
}
_count = 0; //_count定義在類的最頂層與上面的constructor()寫法等價
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}

這種寫法直觀明瞭。


四、靜態屬性

定義靜態屬性:

class Foo {
}
Foo.prop = 1;
Foo.prop // 1

容易被忽略。


五、私有方法和私有屬性

私有方法和私有屬性,是隻能在類的內部訪問的方法和屬性,外部不能訪問。


六、new.target 屬性

ES6 為new命令引入了一個new.target屬性,如果構造函數不是通過new命令或Reflect.construct()調用的,new.target會返回undefined。

Class 內部調用new.target,返回當前 Class。

需要注意的是,子類繼承父類時,new.target會返回子類。


七、Class繼承簡介

class Point {
}
class ColorPoint extends Point {
}
// 等同於
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
  • Class可以通過extend關鍵字實現繼承。super關鍵字表示父類的構造函數,用來新建父類的this對象。
  • 子類必須在constructor方法中調用super方法,這樣才能得到父類的this,否則會報錯。這是因為子類自己的this對象,必須先通過父類的構造函數完成塑造,得到與父類同樣的實例屬性和方法,然後再對其進行加工,加上子類自己的實例屬性和方法。


class ColorPoint extends Point {
}
// 等同於
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
  • 如果子類沒有定義constructor,constructor會被自動添加,而且super也會被自動添加到constructor中。
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true
  • cp同時是Point和ColorPoint的實例。
  • 父類的靜態方法,也會被子類繼承。


七、Object.getPrototypeOf()

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

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

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


八、super關鍵字

super關鍵字,既可以當函數又可以當對象使用,兩種用法有所不同。

3.1.作為調用函數使用:

class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
  • 子類B中的構造函數,必須調用一次super,代表父類的構造函數(相當於繼承父類的方法和屬性),但是這裡的super內部的this指向B。
  • 作為函數時,super()只能用在子類的構造函數之中(只能放在constructor中),用在其他地方就會報錯。

3.2.作為對象使用:

下面例子中的採用super.x就代表把super當做對象使用。

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

class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
A.prototype.x = 2;
class B extends A {
constructor() {
super();
this.x = 3;
console.log(super.x) // 2
}
m(){
super.print();
}
}
let b = new B();
b.m() //3
  • 實例b返回的是2而不是1,說明super指向的是A的原型。
  • b.m()返回的是3,這是因為子類的普通方法調用父類的方法時,this指向的是當前子類實例。所以也可以通過super.x = 4對子類屬性進行賦值。

super作為對象時,在靜態方法中,指向父類:

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
  • Child.myMethod(1)通過類直接調用的方式,調用靜態方法,此時執行static中的super.myMethod,super指向父類Parent。
  • Child.myMethod(2)調用的是普通方法,此時的super指向的是父類Parent的原型,所以調用的是console.log('instance', msg);。
  • 補充:在子類的靜態方法中通過super調用父類的方法時,方法內部的this指向當前的子類,而不是子類的實例。


九、類的 prototype 屬性和__proto__屬性

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

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

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


十、原生構造函數的繼承

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

原生構造函數是指語言內置的構造函數,通常用來生成數據結構。這些原生構造函數是無法繼承的,在ES6中得以實現。

class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined

除此之外,ES6還可以在原生數據結構的基礎上,定義自己的數據結構。


十一、Mixin 模式的實現

Mixin 指的是多個對象合成一個新的對象,新對象具有各個組成成員的接口。

function mix(...mixins) {
class Mix {
constructor() {
for (let mixin of mixins) {
copyProperties(this, new mixin()); // 拷貝實例屬性
}
}
}
for (let mixin of mixins) {
copyProperties(Mix, mixin); // 拷貝靜態屬性
copyProperties(Mix.prototype, mixin.prototype); // 拷貝原型屬性
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== 'constructor'
&& key !== 'prototype'
&& key !== 'name'
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}

上面代碼的mix函數,可以將多個對象合成為一個類。使用的時候,只要繼承這個類即可。

相關推薦

推薦中...