javascript 繼承

什麼是繼承?
繼承就是你爸爸不少錢,因此你就繼承你爸爸,變成了富二代,也是個有錢的主,忽然你爸爸世界盃賭球,輸了個精光,因而你也變成了窮光蛋。這個就是繼承
非也,非也。java

C# 繼承jquery

public class A {
   private int a;
   private int b;
}

public class B :A {
 private int c;
 private int b;
}

//B 繼承 A

ES6 繼承es6

export default class A {
    constructor(props){
       super(props)
    }
    test() { alert('test') }
}

export default class B extends A {
    constructor(props){
       super(props)
    }
    test1() { this.test() }
}

//B 繼承 A

繼承機制的實現

要用 ECMAScript 實現繼承機制,您能夠從要繼承的基類入手。全部開發者定義的類均可做爲基類。出於安全緣由,本地類和宿主類不能做爲基類,這樣能夠防止公用訪問編譯過的瀏覽器級的代碼,由於這些代碼能夠被用於惡意攻擊。
選定基類後,就能夠建立它的子類了。是否使用基類徹底由你決定。有時,你可能想建立一個不能直接使用的基類,它只是用於給子類提供通用的函數。在這種狀況下,基類被看做抽象類。瀏覽器

儘管 ECMAScript 並無像其餘語言那樣嚴格地定義抽象類,但有時它的確會建立一些不容許使用的類。一般,咱們稱這種類爲抽象類。安全

建立的子類將繼承超類的全部屬性和方法,包括構造函數及方法的實現。記住,全部屬性和方法都是公用的,所以子類可直接訪問這些方法。子類還可添加超類中沒有的新屬性和方法,也能夠覆蓋超類的屬性和方法。數據結構

繼承的方式

和其餘功能同樣,ECMAScript 實現繼承的方式不止一種。這是由於 JavaScript 中的繼承機制並非明確規定的,而是經過模仿實現的。這意味着全部的繼承細節並不是徹底由解釋程序處理。做爲開發者,你有權決定最適用的繼承方式。app

1、對象冒充(構造繼承)

所謂"構造函數",其實就是一個普通函數,可是內部使用了this變量。對構造函數使用new運算符,就能生成實例,而且this變量會綁定在實例對象上。函數

原理:構造函數使用 this 關鍵字給全部屬性和方法賦值(即採用類聲明的構造函數方式)。由於構造函數只是一個函數,因此可以使 ClassA 構造函數成爲 ClassB 的方法,而後調用它。ClassB 就會收到 ClassA 的構造函數中定義的屬性和方法。this

function ClassA(name){
    this.name = name;
    this.say = function(){
        console.log(this.name)
    }
}

function ClassB(name){
    this.newSay = ClassA
    this.newSay(name)
    delete this.newSay;
}

爲ClassA賦予了newSay方法(函數名只是指向他的指針),而後調用該方法,傳遞他的是ClassB構造函數的參數name,全部新屬性和新方法都必須在新方法的代碼行後定義,不然,可能回覆蓋超類的相關屬性和方法。es5

運行調用:

var a = new ClassA('one')
var b = new ClassB('two')
a.say() //輸出 'one'
b.say() //輸出 'two'
對象冒充能夠實現多重繼承
function ClassA(name){
    this.name1 = name;
    this.say = function(){
        console.log(this.name1)
    }
}
function ClassB(name){
    this.name2 = name;
    this.method = function(){
        console.log(this.name2) //注意這裏的變量name2 不能和ClassA裏都變量相同,不然回覆蓋掉ClassA的值
    }
}

function ClassC(name1,name2){
    this.newSay = ClassA
    this.newSay(name1)
    delete this.newSay;

    
    this.newMethod = ClassB
    this.newMethod(name2)
    delete this.newMethod
}
// test
var a = new ClassA('one')
var b = new ClassB('two')
var c = new ClassC('three','four')
a.say() // 輸出 'one'
b.method() // 輸出 'two'
c.say() // 輸出 'three'
c.method() // 輸出 'four'

弊端:若是存在兩個類 ClassA 和 ClassB 具備同名的屬性或方法,ClassB 具備高優先級。由於它從後面的類繼承。除這點小問題以外,用對象冒充實現多重繼承機制垂手可得。
因爲這種繼承方法的流行,ECMAScript 的第三版爲 Function 對象加入了兩個方法,即 call() 和 apply()。

call()

function ClassA(name){
    this.name = name;
    this.say = function(){
        console.log(this.name)
    }
}

function ClassB(name){
    ClassA.call(this,name)  //讓ClassA裏面的this等於新建立的ClassB對象
}

//test
var a = new ClassA('one')
var b = new ClassB('two')
a.say() //輸出 'one'
b.say() //輸出 'two'

apply()

function ClassA(name){
    this.name = name;
    this.say = function(){
        console.log(this.name)
    }
}

function ClassB(name){
    ClassA.apply(this,arguments)  //把 ClassB 的整個 arguments 對象做爲第二個參數傳遞給 apply() 方法
}

//test
var a = new ClassA('one')
var b = new ClassB('two')
a.say() //輸出 'one'
b.say() //輸出 'two'

缺點:沒法繼承原型鏈上的屬性和方法

2、原型鏈(prototype chaining)繼承

原理:繼承這種形式在 ECMAScript 中本來是用於原型鏈的,prototype 對象是個模板,要實例化的對象都以這個模板爲基礎,prototype 對象的任何屬性和方法都被傳遞給那個類的全部實例。原型鏈利用這種功能來實現繼承機制。

與對象冒充類似,子類的全部屬性和方法都必須出如今 prototype 屬性被賦值後,由於在它以前賦值的全部方法都會被刪除。爲何?由於 prototype 屬性被替換成了新對象,添加了新方法的原始對象將被銷燬。

注意:調用 ClassA 的構造函數,沒有給它傳遞參數。這在原型鏈中是標準作法。要確保構造函數沒有任何參數。

function ClassA(){ }

ClassA.prototype.name = 'chuchur'
ClassA.prototype.say = function(){
    console.log(this.name)
}

function ClassB(){}
/**
 * 徹底刪除了prototype對象原先的值,而後賦予一個新的值。
 * 此時 Cat.prototype.constructor == Animal   ==>true
 */
ClassB.prototype = new ClassA()

//注意下面一行代碼的意思
/** 
 * 任何一個prototype對象都有一個constructor屬性,指向它的構造函數
 * 沒有ClassB.prototype = new ClassA(),那麼ClassB.prototype.constructor是指向ClassB,加了以後指向ClassA
 * 更重要的是,每個實例也有一個constructor屬性,默認調用prototype對象的constructor屬性。
 * 因此  (new ClassB()).constructor == ClassA ==>true,
 * 這顯然會致使繼承鏈的紊亂,所以咱們必須手動糾正,將ClassB.prototype對象的constructor值改成ClassB,
 * 當每次改變prototype的值時,必然要復原construcotr
*/
ClassB.prototype.constructor = ClassB

ClassB.prototype.name = ''
ClassB.prototype.method = function(){
    console.log(this.name)
}

//test
var a = new ClassA()
var b = new ClassB()
a.name = 'one'
b.name = 'two' //這裏不從新複製,則name的取值爲chuchur,也就是初始化值
a.say() // 輸出 one
b.say() // 輸出 two
b.method() // 輸出 two

缺點:原型鏈的弊端是不支持多重繼承。記住,原型鏈會用另外一類型的對象重寫類的 prototype 屬性。

3、混合方式繼承

這種繼承方式使用構造函數定義類,並不是使用任何原型。對象冒充的主要問題是必須使用構造函數方式,這不是最好的選擇。不過若是使用原型鏈,就沒法使用帶參數的構造函數了。

建立類的最好方式是 用構造函數定義屬性用原型定義方法。這種方式一樣適用於繼承機制,用對象冒充繼承構造函數的屬性,用原型鏈繼承 prototype 對象的方法。

function ClassA(mail){
    this.mail = mail
}
ClassA.prototype.sayMail = function(){
    console.log(this.mail)
}

function ClassB(mail,name){
    ClassA.call(this,mail)
    this.name = name
}
ClassB.prototype = new ClassA()
ClassB.prototype.constructor = ClassB
ClassB.prototype.sayName = function(){
    console.log(this.name)
}

//test
var a = new ClassA('chuchur@qq.com')
var b = new ClassB('dev@chuchur.com','chuchur')
a.sayMail() //輸出 chuchur@qq.com
b.sayMail() //輸出 dev@chuchur.com
b.sayName() //輸出 chuchur

ES5繼承和ES6繼承的區別

class A {
    sayName(){
        console.log('chuchur')
    }
}

class B extends A {
  constructor() {
    super();
  }
}

ES5 的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制徹底不一樣,實質是先將父類實例對象的屬性和方法,加到this上面(因此必須先調用super方法),而後再用子類的構造函數修飾this,使得父類的全部行爲均可以繼承。

super做爲函數調用時,表明父類的構造函數。ES6 要求,子類的構造函數必須執行一次super函數。

注意,super雖然表明了父類A的構造函數,可是返回的是子類B的實例,即super內部的this指的是B,所以super()在這裏至關於A.prototype.constructor.call(this)。

super還能夠被看成對象使用,這時,super在普通方法之中,指向A.prototype,因此super.sayName()就至關於A.prototype.sayName()。

class A {
    sayName(){
        return 'chuchur'
    }
}

class B extends A {
  constructor() {
    super();
    console.log(super.sayName())
  }
}

ES6 容許繼承原生構造函數定義子類,能夠自定義原生數據結構(好比Array、String等)的子類,這是 ES5 沒法作到的。extends關鍵字不只能夠用來繼承類,還能夠用來繼承原生的構造函數。所以能夠在原生數據結構的基礎上,定義本身的數據結構.

class SuperArray extends Array {
  constructor() {
    super();
    this.history = [[]];
  }
  commit() {
    this.history.push(this.slice());
  }
  revert() {
    this.splice(0, this.length, ...this.history[this.history.length - 1]);
  }
}

var x = new SuperArray();

x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]

x.commit();
x.history // [[], [1, 2]]

類的 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,子類B的prototype屬性的__proto__屬性指向父類A的prototype屬性。

用圖形來表示
ES5繼承

ES6繼承

實例講解

請轉到這篇文章:js-繼承-jquery

相關文章
相關標籤/搜索