「總結」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函數,可以將多個對象合成為一個類。使用的時候,只要繼承這個類即可。