javascript 對象--繼承

哇哦~今天迎來的是掘金後臺編寫文章的新頁面,不知道這個頁面是須要經過寫文章才能打開的,仍是新上的啊哈哈啊哈javascript

本篇由來

其實本身平時在總結上是很是注重的,可是通常是總結的比較隨意,會存放在本身的筆記軟件中,推薦使用「語雀」,劃分目錄和頁面的美觀交互深得我心,固然,適合本身的一個筆記軟件纔是最重要的;接下來的半個月內,我也將從新回顧本身總結的知識點和項目中遇到的困難點,並整理到掘金文章中html

切入主題 對象

談起對象,實際上是一個很需的概念,在大學課程java學習中就留下了一個很是清晰的詞語「面向對象」編程,對於對象而言,不只僅是去建立、去繼承,與之相呼應的還有一些理論性的名詞,這些名詞在javascript中多是不全體現的,更多體如今java語言程序中;java

理論名詞介紹

我忽然想起大學考試卷子填寫概念的場景了 🤯🤯🤯typescript

對象

       所謂對象,本質上就是指事物(包括人和物)在程序設計語言中的表現形式。這裏的事物能夠是任何的東西(如客觀存在的對象,或者較爲抽象的概念);
       好比 小狗,咱們將其理解成對象,它是具備某些明確的特徵的(體重、顏色、名字等),除此以外,還可以執行某些動做(汪汪叫、吃飯、睡覺等),在javascript語言中這些明確的某些特徵就是屬性,可以執行的行爲就是方法編程

        類就是具有某些共同特徵的實體的集合,它是一種抽象的數據類型,它是對所具備相同特徵實體的抽象。在面向對象的程序設計語言中,類是對一類「事物」的屬性與行爲的抽象。
好比:「老人」、「兒童」、「青年「、「中年人「是關於人的年齡層段的劃分,他們的共有的某些特徵,其實能夠抽成「人」,segmentfault

image.png

圖1: 類間關係

繼承

    在傳統的OOP環境中,繼承一般指的是類與類之間的關係,但因爲javascript中不存在類,所以它的繼承只能發生在對象之間;
    當一個對象繼承自另外一個對象時,一般會往其中加入新的方法,以擴展被繼承的老對象。一般將這一過程稱之爲「B繼承自A」或「B擴展自A」。另外對於新對象來講,它能夠根據本身的須要,從繼承的那組方法中選擇幾個來從新定義。這樣作並不會改變對象的接口,由於其方法名是相同的,只不過當調用新對象時,該方法的行爲與以前不一樣了markdown

封裝

封裝主要用於闡述對象中所包含的內容。封裝概念一般由兩部分組成:app

  • 相關的數據(用於存儲屬性)
  • 基於這些數據所能作的事(所能調用的方法)

    封裝的目的是將信息隱藏,即方法與屬性的可見性。通常而言,封裝包括封裝數據和封裝實現     在許多語言的對象系統中,封裝數據是由語法解析來實現的,這些語言提供了public、private、protected這些關鍵字來限定方法和屬性的可見性,這種限定分類定義了對象用戶所能訪問的層次; 但javascript並無提供對這些關鍵字的支持,只能依賴變量的做用域來實現封裝特性, 並且只能模擬出 public 和 private 這兩種封裝性。除了ECMAScript6中提供的let以外,通常經過函數來建立做用域:不過typescript就另當別論了;ide

多態

多態的實際含義是:同一個操做做用於不一樣的對象上面,能夠產生不一樣的解釋和不一樣的執行結果。換句話說,給不一樣的對象發送同一個消息的時候,這些對象會根據這個信息分別給出不一樣的反饋; 本身停留理解不夠深入,就不班門弄斧了,直接推薦連接閱讀函數

理論總結

  • 對象:marry是一個學生(學生是一個對象)
  • 屬性:marry是女生,黑頭髮、高個子
  • 方法:marry能學習、吃飯、睡覺
  • 類:marry是Person類的一個實例
  • 原型對象:marry是一個由Person對象擴展而來的新對象
  • 封裝:marry對象包含了數據和基於這些數據的方法
  • 繼承:marry,xiaoli,xiaotong都是分別擴展自Person對象的新對象
  • 多態:能夠隨時調用:marry,xiaoli,xiaotong這三個對象各自的talk方法,它們均可以正常工做,儘管這些方法會產生不一樣的結果。marry注重頁面設計,xiaoli注重頁面實現,xiaotong注重頁面測試

javascript繼承實現

javascript的繼承方式也有不少種,平常經常使用的方法也是很是多,而且不少書籍中,也有介紹繼承的方法,本文參考《javascript 高級程序設計(第三版)》

原型鏈繼承

在咱們瞭解的JavaScript中,對於原型鏈的使用是很是多的,被建立的對象能夠經過原型鏈訪問上級的對象,而原型鏈繼承的方式正式運用了此訪問關係;

原型鏈繼承實現

function Super() {
      this.value = true;
    }
    Super.prototype.getValue = function () {
      return this.value;
    }
    function Sub() { }
    console.log("super", new Super())
    //Sub繼承super
    Sub.prototype = new Super();
    //將sub的構造函數指向 Sub
    Sub.prototype.constructor = Sub;
    //建立實例
    var instance = new Sub();
    console.log('sub', instance)

    console.log(instance.getValue())
複製代碼
圖2 原型鏈繼承的關係

subSuper以及instance之間關係是:

圖3 sub和Super繼承的鏈式關係

原型鏈繼承存在問題

原型鏈最主要的問題在於包含引用類型值的原型屬性會被全部實例共享,而這也正是爲何要在構造函數中,而不是在原型對象中定義屬性的緣由。 在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例。因而,原先的實例屬性也就瓜熟蒂落地變成了如今的原型屬性了;
構造函數包含引用類型

function Super1() {
      this.colors = ['red', 'blue', 'green'];
    }
    function Sub1() { };
    //Sub繼承了Super
    Sub1.prototype = new Super1();
    var instance1 = new Sub1();
    //查看instance1中的屬性
    console.log('instance1',instance1)
    instance1.colors.push('black');
    console.log('instance1.colors',instance1.colors);//'red,blue,green,black'
    var instance2 = new Sub1();
    console.log('instance2 經過Sub1構造出來的',instance2.colors);//'red,blue,green,black'
複製代碼
圖4 上級構造函數包含引用類型會被修改
  • 原型鏈繼承方式,對於複雜數據類型是引用關係,不是每一個構造函數中單獨享有;
  • 建立子類型的是互毆,不能向超類型的構造函數中傳遞參數,在不少的狀況下,實際上是須要傳遞參數場景的

原型繼承

藉助原型能夠基於已有的對象來建立新對象,同時沒必要所以建立自定義類型;
具體的實現方式;

原型繼承實現

function object(o) {
      function F() {   };
      F.prototype = o;
      return new F();
    }
    var superObj = {
      init: function (value) {
        this.value = value;
      },
      getValue: function () {
        return this.value;
      }
    }

    var subObj = object(superObj);
    console.log('subObj',subObj)
    subObj.init('sub');
    console.log('subObj.getValue',subObj.getValue());//'sub'
複製代碼

與原型鏈繼承的關係;它們的一個重要區別是父類型的實例對象再也不做爲子類型的原型對象; 這種方式更相似於Object.create()的實現

function Super(){
        this.value = 1;
    }
    Super.prototype.value = 0;
    function Sub(){};

    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;

    //建立子類型的實例對象
    var instance = new Sub;
    console.log(instance.value);//0
複製代碼
function F(){};
F.prototype = Super.prototype;
Sub.prototype = new F();
複製代碼

由上面代碼看出,子類的原型對象是臨時類F的實例對象,而臨時類F的原型對象又指向父類的原型對象;因此,實際上,子類能夠繼承父類的原型上的屬性,但不能夠繼承父類的實例上的屬性; 原型繼承和原型鏈繼承都是共享父例引用類型的值;

查看引用類型引用問題

function object(o) {
      function F() { };
      F.prototype = o;
      return new F();
    }
    var superObj = {
      colors: ['red', 'blue', 'green']
    };
    var subObj1 = object(superObj);
    subObj1.colors.push("black");

    var subObj2 = object(superObj);
    subObj2.colors.push("white");

    console.log('superObj.colors',superObj.colors);
    console.log('subObj1.colors',subObj1.colors);
複製代碼
圖5 原型繼承引用問題
😂😂 引用的問題 愛咱們如初

借用構造函數

借用構造函數,很是深入明瞭了,實際上是藉助super的屬性,即在子類型構造函數的內部調用超類型構造函數,經過使用apply()和call()方法在新建立的對象上執行構造函數;

借用構造繼承實現

//借用構造函數
    function Super2() {
      console.log("Super2執行了")
      this.colors = ['red', 'blue', 'green'];
    } 
    function Sub2() {
      //繼承了Super
      Super2.call(this);
    }
    var instance2 = new Sub2();
    instance2.colors.push('black');
    console.log('instance2.colors',instance2.colors);// ['red','blue','green','black']
    var instance3 = new Sub2();
    console.log('instance3.colors',instance3.colors);// ['red','blue','green']
複製代碼
圖6 借用構造函數執行

此時咱們的引用類型也不會存在衝突啦,這個主要的緣由仍是由於當咱們Sub2中執行的時候,Super2的當前執行做用域在Sub2中,造成了內部的變量關係;

借用構造函數的缺點

  • Super2中的原型上的方法是沒法繼續使用的
  • 不符合咱們繼承的實現目的

寄生組合式繼承

借用構造函數可以幫助咱們解決對象引用問題,原型鏈繼承可以幫助咱們解決調用原型方法問題,若是將這兩種方式組合在一塊兒;豈不美哉~ 原型鏈繼承+借用構造函數繼承=既能夠獲取構造函數中的方法,也可以斷引用;
組合繼承(combination inheritance)有時也叫僞經典繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮兩者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,既經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性;

寄生組合式繼承實現

var count = 0; //用於計數
    function Super(name) {
      console.log('Super==count',count++)
      this.name = name;
      this.colors = ['red', 'blue', 'green'];
    }
    Super.prototype.sayName = function () {
      console.log('Super==this.name',this.name);
    };

    function Sub(name, age) {
      console.log('Sub==count',count++) 
      // 第二次調用Super(),Sub.prototype又獲得了name和colors兩個屬性,並對上次獲得的屬性值進行了覆蓋
      Super.call(this, name);
      this.age = age;
    }
    //繼承方法
    // 第一次調用Super(),Sub.prototype獲得了name和colors兩個屬性
    Sub.prototype = new Super();
    Sub.prototype.constructor = Sub;

    Sub.prototype.sayAge = function () {
      console.log('Sub===this.age',this.age);
    }
    
    var instance1 = new Sub("bai", 29);
    instance1.colors.push("black");
    console.log('instance1.colors',instance1.colors);//['red','blue','green','black']
    instance1.sayName();//"bai"
    instance1.sayAge();//29
    console.log('Super',new Super())
複製代碼
圖7 寄生組合式 關係

寄生組合式優缺點

  • 優勢
    • 定義在Super中引用類型數據獨立
    • 可以向構造函數Super中傳入自定義的參數
  • 缺點
    • Super調用次數問題,繼承時候調用一次,實例化Sub後再次調用了Super
    • 每次實例化Sub的時候都會調用Super.call

寄生式繼承

寄生組合式繼承帶來的影響須要調用兩次Super。寄生組合式繼承與組合繼承類似,都是經過借用構造函數來繼承不可共享的屬性,經過原型鏈的混成形式來繼承方法和可共享的屬性。只不過把原型繼承的形式變成了寄生式繼承。使用寄生組合式繼承能夠沒必要爲了指定子類型的原型而調用父類型的構造函數
寄生式繼承只繼承了父類型的原型屬性,而父類型的實例屬性是經過借用構造函數的方式來獲得的;也就是 咱們借用Super的構造函數,可是去複製Super的原型函數;

寄生式繼承實現

function Super(name) {
      console.log("Super === 執行")
      this.name = name;
      this.colors = ["red", "blue", "green"];
    }
    Super.prototype.sayName = function () {
      console.log("this.name",this.name)
      return this.name;
    };

    function Sub(name, age) {
      //第一次 借用構造函數
      Super.call(this, name);
      this.age = age;
    }
    
    //繼承的方法 就是拷貝原型
    if (!Object.create) {
      Object.create = function (proto) {
        function F() { };
        F.prototype = proto;
        return new F();
      }
    }
    // 利用複製原型的方式進行操做 將Super.prototype的原型方法經過實例化給Sub
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;

    var instance1 = new Sub("bai", 29);
    instance1.colors.push("black");
    console.log('instance1.colors',instance1.colors);//['red','blue','green','black']
    instance1.sayName();//"bai"

    var instance2 = new Super("hu", 27);
    console.log("instance2.colors",instance2.colors);//['red','blue','green']
    instance2.sayName();//"hu"
複製代碼
圖8 寄生式原型繼承

這三者的鏈式關係

圖9 寄生原型繼承
這三者的關係其實很難畫出來😞😞 不過可以知道的是,每次實例化```Sub```時候都是造成的獨立做用域,兩個對象互不影響;

參考文檔

ps:很久以前整理的,查過不少文檔博客內容,可是沒有一一記錄下來,我仍是很尊重知識來源的的🥳🥳

相關文章
相關標籤/搜索