1. 程式人生 > 其它 >關於JS繼承

關於JS繼承

關於JS繼承

關於JS繼承

關於繼承這個概念,是由面向物件衍生出來的。而JS身為一種基於面向物件而設計的語言,它和正統的面嚮物件語言又是有差別的。

面嚮物件語言

身為面嚮物件語言需要對開發者提供四種能力:
①:封裝 - 把相關的資訊(無論資料或方法)儲存在物件中的能力;
②:聚集 - 把一個物件儲存在另一個物件內的能力;
③:多型 - 編寫能以多種方法執行的函式或方法的能力;
④:繼承 - 有另一個或多個類得來類的屬性和方法的能力;
雖然JS都具備這種能力,然而它卻和正統的面嚮物件語言有差異,具體表現主要在第③、④點。

多型

多型主要有兩種實現形式:
①:過載(編譯時多型性) - 方法名相同,形參個數或型別不同;
②:重寫(執行時多型性) - 在繼承中,子類重寫父類的方法;
這兩種形式上JS和java/C#是表現形式是不一樣的。
過載:

// java程式碼示例
public void sum(int n, float m){

}
public void sum(int n, float m, int t){
    
}
public void sum(int n, float m, String t){
    
}

在java中這sum方法會分別在不同情況下呼叫。

// js程式碼示例
function sum(n,m){

}
function sum(n,m,t){

}

在JS中sum方法在前面定義的會被後面定義的覆蓋,因此JS用相同方法處理不用情況只能在sum方法內部對資料做識別判斷了。
重寫:

//java程式碼示例
public class Main {

    public static void main(String[] args) {
        Student a = new Student();
        a.say();
    }
}

class person{
    public void say(){
        System.out.println("11111");
    }
}

class Student extends person{
    public void say(){
        System.out.println("22222");
    }
}

這裡Student繼承了person,對say方法進行了重寫,雖然重寫了say方法,但是父類方法不會被改變。

//js程式碼示例
function A(m){
    this.m = m;
}
A.prototype.getM = function(){
    console.log(100,this.m)
}

function B(n){
    this.n = n;
}
B.prototype = new A(100);
B.prototype.getN = function(){
    console.log(this.n)
}

let b = new B(200);
b.getM();   //列印 100,100

b.getM = function(){
    console.log(1)
}
b.getM();   //列印 1
A.prototype.getM(); //列印 100,undefined

b.__proto__.__proto__.getM = function(){
    console.log(0,this.m)
}; 
b.getM();   //列印 1
A.prototype.getM(); //列印 0,undefined

而JS對繼承的父類的重寫就有點不一樣了,JS是基於原型鏈的繼承。呼叫方法或物件時會在原型鏈中追溯,而當在例項b中添加了getM方法時,其實是攔截了原型鏈上的getM,因此這種攔截不會改動到A.prototype.getM,然而A.prototype.getM又不是絕對的私密的,在示例中可以看到利用b.__proto__.__proto__.getM重寫getM是會導致A.prototype.getM也被重寫的,所以js原型鏈繼承中是沒有完整意義上的私密屬性的。

繼承

和其他正統的面嚮物件語言不一樣,其他語言的繼承一般都是拷貝繼承,也就是子類會把父類中的方法和屬性拷貝的子類中實現繼承,而JS的繼承是把父類的原型放在子類的原型鏈上,利用原型鏈的查詢機制實現繼承的。
而且JS的繼承方式也是很多的,下面列舉一下比較常見的繼承方式。

一、原型鏈繼承
function A(m){
    this.m = m;
}
A.prototype.getM = function(){
    console.log(this.m)
}

function B(n){
    this.n = n;
}
B.prototype = new A(100);
B.prototype.getN = function(){
    console.log(this.n)
}

let b = new B(200);
b.getN();   //輸出 200
b.getM();   //輸出 100

特點:
1、非常純粹的繼承關係,例項是子類的例項,也是父類的例項;
2、父類新增屬性或其原型新增屬性,子類都能訪問;
缺點:
1、子類能重寫父類方法,如:b.__proto__.__proto__.getM = null,可以改寫父類方法;
2、父類的私有屬性和公有屬性都變成子類的公有屬性;
3、繼承時方法B原型上原有的方法或屬性會丟失,如:constructor建構函式;(解決:B.prototype = B);
4、子類建立例項時無法向父類傳參;
5、無法實現多繼承;

二、建構函式繼承
function A(m, n){ 
    this.m=m; 
    this.n=n;
} 
function B(m, n, t){
    A.call(this, m, n); 
    this.t=t;
} 
var b = new B(1, 2, 3);
console.log(b.m, b.n, b.t);

特點:
1、能在子類建立例項時向父類傳遞引數;
2、可以實現多繼承;
缺點:
1、只能繼承父類的屬性(不繼承原型鏈);
2、建立的例項攜帶著父類屬性,臃腫;

三、寄生組合繼承
function B(n){
    this.n = n;
}
B.prototype.getB = function(name){
    console.log(this.n, name);
}

function A(m){
    B.call(this,200)
    this.m = m;
}

// 我是舊時代的殘黨,新時代沒有適合承載我的船
// (function(){
//   // 建立一個沒有例項方法的類
//   var Super = function(){};
//   Super.prototype = B.prototype;
//   //將例項作為子類的原型
//   A.prototype = new Super();
// })();

// ECMAScript 5 通過新增 Object.create()方法規範化了原型式繼承。
A.prototype = Object.create(B.prototype);

A.prototype.constructor = A;
A.prototype.getA = function(name){
    console.log(this.m, name)
}

var aa = new A(100);
aa.getA("A");
aa.getB("B");

特點:
1、可以繼承父類的屬性及其原型上的屬性;
2、既是子類的例項,也是父類的例項;
3、不存在引用屬性共享問題;
4、父類可接收傳參;