1. 程式人生 > 程式設計 >JS面向物件程式設計——ES6 中class的繼承用法詳解

JS面向物件程式設計——ES6 中class的繼承用法詳解

本文例項講述了 ES6 中class的繼承用法。分享給大家供大家參考,具體如下:

JS是一種基於物件的語言,要實現面向物件,寫法跟傳統的面向物件有很大的差異。ES6引入了Class語法糖,使得JS的繼承更像面嚮物件語言的寫法。

此篇部落格,分為:基本介紹、Vue使用案例

基本介紹

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

class Father {
 }
class Son extends Father {
}

程式碼定義了一個Son 類,該類通過extends關鍵字,繼承了Father類的所有屬性和方法,但是由於沒有部署任何程式碼,所以這兩個類完全一樣,等於複製了一個Father類。

class Son extends Father {
     constructor (name,age,city) {
        super(name,age);//呼叫父類的constructor(name,age);
        this.city = city;
      }
 
      toString () { 
         return this.city+ " " +super.toString();//呼叫父類的toString()
      }
}

constructor方法和toString方法之中,都出現了super關鍵字,他在這裡表示父類的建構函式,用來新建父類的this物件;

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

class Father {  }
 
class Son extends Father {
     constructor(){ }
}
let s = new Son();
//referenceError : this is not defined 

Son繼承了父類Fatherm,但是他的建構函式沒有呼叫super方法,這導致新建例項時報錯;
ES5的繼承,實質是先創造子類的例項物件this,然後再將父類的方法新增到this上(Parent.apply(this)),ES6的繼承機制完全不同,實質是先創造父類的例項物件this(所以必須先呼叫super方法),然後再用子類的建構函式修改this;


如果子類沒有定義constructor方法,這個方法會預設新增,也就是說,不管有沒有顯式定義,任何一個子類都有constructor方法。

class Son extends Father {
}
 
//等同於
class Son extends Parent {
    constructor(...args) {
    super(...args);
   }
}

另一個需要注意的是:在子類的建構函式中,只有呼叫super之後,才能使用this關鍵字,否則會報錯。這是因為子類例項的構建,是基於對父類例項加工,只有super方法才能返回父類例項;

class Father {
   constructor (x,y) {
      this.x= x;
      this.y = y;
    }
}
 
class Son extends Father {
   constructor (x,y,color) {
       this.color =color ;//ReferenceError : this is not defined
      super(x,y);
       this.color = color;//正確
      }
}
 
let s = new Son(25,8,"green");
s instanceof Son //true 
s instanceof Father //true

子類的constructor方法沒有呼叫super之前,就使用this關鍵字,結果報錯,而放在super方法之後就是正確的;

Object.getPrototypeOf()方法用來從子類上獲取父類

Object.getPrototypeOf( Son ) ===Father
//true
//因此可以用這個方法判斷,一個類是否繼承了另一類

super 關鍵字
super這個關鍵字,既可以當作函式使用,也可以當作物件使用,
(1)第一情況是:super當作函式呼叫時,代表父類的建構函式,ES6要求,子類的建構函式必須執行一個super函式;

class Father { }
 
class Son extends Father {
    constructor () {
          super();
       }
}
//子類Son的建構函式之中的super(),代表呼叫父類的建構函式。這是必須的,否則 JavaScript 引擎會報錯。

super雖然代表了父類Father的建構函式,但是返回的是子類Son的例項,即super內部的this指向的是Son,因此super()在這裡相當於Father.constructor.call(this);
而且作為函式時,super()只能用在子類的建構函式中,在其他地方會報錯;

class A {
     constructor (){
        console.log(new.target.name);
      }
 }
 
class B extends A {
   constructor () {
      super();
      }
 }
  new A()//A
 new B()//B 

new.target指向當前正在執行的函式,在super()執行時,他指向的是子類B的建構函式,而不是父類A的建構函式,super()內部的this指向的是B;

(2)第二種情況,super作為物件時,在普通方法中,指向父類的原型物件,在靜態方法中,指向父類;

class Father{
   getName ( ) {
     return "MGT360124";
   }
}
class Son extends Father {
    constructor () {
    super();
    console.log(super.getName() ) //“MGT360124”
    }
}
let s = new Son();

子類Son中的super.p()就是將super當作一個物件使用,這時,super在普通方法中,指向Father.prototype,所以super.getName()就相當於Father.prototype.getName();//"MGT360124",由於super指向父類的原型物件,所以定義在父類例項上的方法或者屬性,是無法通過super呼叫的;

class Father {
   constructor () {
       this.p =2
     }
}
 
class Son extends Father {
     get m ( ) {
          return super.p;
     }
     getValue ( ) {
          return super.a;
      }
}
let s = new Son();
s.m
//undefined

p是父類Father例項的屬性,super.p就引用不到它

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

class A {}
A.prototype.x = 2;
 
class B extends A {
 constructor() {
  super();
  console.log(super.x) // 2
 }
}
 
let b = new B();

屬性x是定義在A.prototype上面的,所以super.x可以取到它的值。

ES6 規定,通過super呼叫父類的方法時,super會繫結子類的this。

class Father {
   constructor () {
      this.x =1;//這個this指向的是Father物件的例項
   }
   print () {
      console.log(this.x);
   }
}
 
class Son extends Father {
   constructor () {
       super();
        this.x = 2;//這個this指向的是Son物件的例項
   }
     m() {
      super.print();    
     }
}
let s = new Son();
s.m();
//2 

super.print()雖然呼叫的是Father.prototype.print(),但是Father.prototype.print()會繫結子類Son的this,導致輸出的是2,而不是1,也就是說,實際上執行的是 super.print.call(this)。

如果super作為物件,用在靜態方法中,這時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

super在靜態方法之中指向父類,在普通方法之中指向父類的原型物件。
注意,使用super的時候,必須顯式指定是作為函式、還是作為物件使用,否則會報錯。
類的prototype屬性和proto屬性
大多數瀏覽器的ES5實現之中,每一個物件都有proto屬性,指向對應的建構函式的prototype屬性,class作為建構函式的語法糖,同時有prototype屬性和proto屬性,因此同時存在兩條繼承鏈;
(1)子類的proto屬性,表示建構函式的繼承,總是指向父類;
(2)子類prototype屬性的proto屬性,表示方法的繼承,總是指向父類的prototype屬性;

class A{
}
class B{
}
//B的例項繼承A的例項
Object.setPrototypeOf(B.prototype,A.prototype);
 
//B 的例項繼承A的靜態屬性
Object.setPrototypeOf(B,A);
 
const b = new B();

《物件的擴充套件》一章中Object.setPrototypeOf()方法的實現:

Object.setPrototypeOf = function (obj,proto) {
obj.__proto__ = proto;
  return obj ;
}

因此

Object.setPrototypeOf( B.prototype,A.prototype );
//等同於
B.prototype.__proto__ = A.prototype ;
 
Object.setPrototypeOf(B,A);
//等同於
B.__proto__ = A;

這兩條繼承鏈,可以理解為:作為一個物件,子類B的原型(proto屬性)是父類(A);作為一個建構函式,子類B的原型物件(prototype屬性)是父類的原型物件(prototype)的例項;

extends的繼承目標
extends關鍵字後面可以跟很多型別的值;

class B extends A{
}

只要A有一個prototype屬性的函式,就能被B繼承,由於函式都有prototype屬性(除了Function.prototype函式),因此A可以使任意函式,下面三種情況:
(1)子類繼承Object類

class A extends Object {
}
A.__proto__ === Object //true;
A.prototype.__proto__ === Object.prototype //true

這種情況就是 : A就是建構函式Object的複製,A的例項就是Object的例項
(2)不存在任何繼承

class A {
}
A.__proto__ === Function.prototype //true
A.prototype.__proto__ = Object.prototype //true

這種情況是:A作為一個基類(不存在任何繼承),就是一個普通的函式,所以直接繼承Function.prototype。但是A呼叫後返回一個空物件(即Object例項),所以A.prototype.proto指向建構函式(Object)的prototype屬性;
例項的proto屬性
子類例項的proto屬性的proto屬性,指向父類例項的proto屬性。也就是說,子類的原型的原型,是父類的原型。

原生建構函式的繼承
原生建構函式是指語言內建的建構函式,通常用來生成資料結構。

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

extends關鍵字不僅可以用來繼承類,還可以用來繼承原生的建構函式。因此可以在原生資料結構的基礎上,定義自己的資料結構。

vue使用

testClass.js

//定義類
class Person{ 
	// 構造 
	constructor(x,y){ 
		this.x = x; 
		this.y = y; 
	} 
 
  //定義在類中的方法不需要新增function
	toString(){ 
		return (this.x + "的年齡是" +this.y+"歲"); 
	} 
} 
export {
	Person
}; 

test.vue

<template>
	<div>
		<p id="testJs"></p>
	</div>
</template>
<script>
import {Person} from './testClass.js'; 
export default {  
	data() {
		return {
		}
	},mounted(){
		let text=document.getElementById("testJs");
		//使用new的方式得到一個例項物件
		let person = new Person('張三',12); 
		text.innerHTML=person.toString();//張三的年齡是12歲
		console.log(typeof Person);//function 
	}
}
</script>

感興趣的朋友可以使用線上HTML/CSS/JavaScript程式碼執行工具:http://tools.jb51.net/code/HtmlJsRun測試上述程式碼執行效果。

更多關於JavaScript相關內容感興趣的讀者可檢視本站專題:《javascript面向物件入門教程》、《JavaScript錯誤與除錯技巧總結》、《JavaScript資料結構與演算法技巧總結》、《JavaScript遍歷演算法與技巧總結》及《JavaScript數學運算用法總結》

希望本文所述對大家JavaScript程式設計有所幫助。