1. 程式人生 > 實用技巧 >深入JavaScript prototype

深入JavaScript prototype

原型與原型鏈

我覺得,直接倆一張圖就好了

建構函式

在上圖中,Person是一個建構函式,一般情況下,它長這個樣子。

function Person() {
  
}

什麼是建構函式呢?建構函式,是一種特殊的方法,主要用來在建立函式的時候初始化物件。

原型

原型,prototye,是一個物件,這個物件能夠給例項共享屬性與方法。如圖中的Person.prototype,就是Person的原型物件。

例項

通過new關鍵字生產的新的物件例項,它有一個指標_proto_,通常我們稱為隱式原型,從例項指向Person.prototype。

因此,就有了person._prototype_ === Person.prototype

最初的js是不支援__proto__的,但因為絕大部分瀏覽器都支援這個屬性,所以它才被加入到了ES6裡。不得不低頭。

構造器

Person.prototype,即原型物件裡面,有一個constructor屬性,這個constructor屬性指向了原型所關聯的建構函式,即Person,我們可以通過以下例子來驗證這一點。

Person.prototype.constructor === Person	// true

原型的原型的原型的原型....原型...Object(原型鏈)

原型是一個物件,既然是物件,我們肯定可以通過new Object來建立。所有原型物件都是通過Object建立來的,然而:

console.log(Object.prototype.__proto__ === null) // true

說明Object是原型的原型的原型...的盡頭。

  • 我們在查詢一個例項的屬性的時候,它會在當前例項的屬性中找;
  • 倘若找不到,便會到它的原型物件裡面去找;
  • 倘若還是找不到,然後會到它的原型物件的原型物件去找,知道找到頂層的Object為止。

這樣相互關聯的原型組成的鏈狀結構就是原型鏈,也就是藍色的這條線。

注意:當獲取person.constructor的時候,person的constructor不存在於person中,於是便會去person.prototype中查詢constructor,這時候 person.constructor === Person.prototype.constructor

如何用原型實現繼承

  • 類式繼承

    function SuperClass() {
      this.name = 'father';
    }
    SuperClass.prototype.getSuperName = function() {
      console.log(this.name);
    }
    function SubClass() {
      this.name = 'son';
    }
    SubClass.prototype = new SuperClass();
    SubClass.prototype.getSubName = function() {
      console.log(this.name);
    }
    var instance = new SubClass();
    console.log(instance instanceof SubClass);
    console.log(instance instanceof SuperClass);
    console.log(SubClass instanceof SuperClass);
    

    類式繼承雖然實現起來很簡單,但是這種繼承方式有兩個缺點:

    1. 由於子類通過其原型對父類例項化,繼承了父類,所以說父類中如果共有屬性是引用型別,那麼就會在子類中被所有的例項所共享。一個自類對其修改,就會影響到其他的子類。

      1. 由於子類實現的繼承是靠其原型prototype對父類進行例項化實現的,因此在創造父類的時候,是無法向父類傳遞引數的。因而在例項化父類的時候也無法對父類建構函式內的屬性進行初始化
      2. 這種繼承沒有涉及到prototype,因此,父類prototype原型鏈的方法也不會被繼承。
  • 建構函式繼承

    function SuperClass(name) {
      this.skills = ['javascript','css', 'html'];
      this.name = name;
    }
    SuperClass.prototype.getName = function() {
      console.log(this.skills);
    }
    function SubClass(name) {
      //繼承父類
      SuperClass.call(this, name);
    }
    //建立第一個子類例項
    var instance1 = new SubClass('jack');
    //建立第二個子類例項
    var instance2 = new SubClass('bob');
    
    instance1.skills.push('java');
    console.log(instance1)
    console.log(instance2)
    instance1.getName();//TypeError
    

    SuperClass.call(this,id)當然就是建構函式繼承的核心語句了.由於父類中給this繫結屬性,因此子類自然也就繼承父類的共有屬性。由於這種型別的繼承沒有涉及到原型prototype,所以父類的原型方法自然不會被子類繼承,而如果想被子類繼承,就必須放到建構函式中,這樣創建出來的每一個例項都會單獨的擁有一份而不能共用,這樣就違背了程式碼複用的原則,所以綜合上述兩種,我們提出了組合式繼承方法

  • 組合式繼承

    說白了就是再指一下原型

    function SuperClass(name) {
      this.skills = ['javascript','css', 'html'];
      this.name = name;
    }
    SuperClass.prototype.getName = function() {
      console.log(this.skills);
    }
    function SubClass(name) {
      //繼承父類
      SuperClass.call(this, name);
    }
    
    // new 出一個SuperClass的例項,然後指定SubClass的原型
    SubClass.prototype = new SuperClass();
    
    //建立第一個子類例項
    var instance1 = new SubClass('jack');
    //建立第二個子類例項
    var instance2 = new SubClass('bob');
    
    instance1.skills.push('java');
    console.log(instance1)
    console.log(instance2)
    instance1.getName();
    

    這樣功能正常了,就是有點醜,兩個subClass,程式碼量偏多

  • 原型式繼承

    function inheritObject(o) {
      function F(){};
      F.prototype = o;
      return new F();
    }
    
    var person = {
      name: 'name',
      age: '18'
    }
    var bob = inheritObject(person);
    bob.name = 'bob';
    bob.age = 20;
    
    var jack = inheritObject(person);
    console.log(bob);
    console.log(jack);
    
    

    如上程式碼我們可以看出,原型式繼承和類式繼承一個樣子,對於引用型別的變數,還是存在子類例項共享的情況。

  • 寄生式繼承

    var person = {
      name: 'name',
      age: 18
    }
    
    function inheritObject(o) {
      function F(){};
      F.prototype = o;
      return new F();
    }
    
    function createPerson(obj) {
      // 通過原型方式建立新的物件
      var o = new inheritObject(obj);
      // 拓展新物件
      o.getName = function(name) {
      	console.log(name);
      }
      // 返回拓展後的新物件
      return o;
    }
    

    多套了一層,可以在createPerson中新增方法屬性等。

  • 寄生組合式繼承

    function inheritObject(o) {
      //宣告一個過渡物件
      function F() { }
      //過渡物件的原型繼承父物件
      F.prototype = o;
      //返回過渡物件的例項,該物件的原型繼承了父物件
      return new F();
    }
    
    function inheritPrototype(subClass,superClass) {
      // 複製一份父類的原型副本到變數中
      var p = inheritObject(superClass.prototype);
      // 修正因為重寫子類的原型導致子類的constructor屬性被修改
      p.constructor = subClass;
      // 設定子類原型
      subClass.prototype = p;
    }
    
    //////////////////////////////////////////////////////////////////////////////////////
    function SuperClass(name) {
      this.name = name;
      this.books=['js book','css book'];
    }
    SuperClass.prototype.getName = function() {
      console.log(this.name);
    }
    function SubClass(name,time) {
      SuperClass.call(this,name);
      this.time = time;
    }
    inheritPrototype(SubClass,SuperClass);
    SubClass.prototype.getTime = function() {
      console.log(this.time);
    }
    var instance1 = new SubClass('React','2017/11/11')
    var instance2 = new SubClass('Js','2018/22/33');
    
    instance1.books.push('test book');
    
    console.log(instance1.books,instance2.books);
    instance2.getName();
    instance2.getTime();