1. 程式人生 > >原型繼承和應用

原型繼承和應用

原型繼承是js的一種繼承方式,原型繼承是什麼意思呢,其實就是說建構函式和子建構函式,或者說類和子類之間(當然js中不存在類),唯一的繼承傳遞方式是通過原型,而不是其他語言直接通過extends(當然ES6的語法糖出現了extends)。所以你需要手寫prototype。(封裝手寫prototype的方法請看我的另一篇文章詳解js中extend函式) 我們先看一個簡單的例子

   function Parent(){
      this.job = 'teacher';
   }
   Parent.prototype.showJob = function(){
      alert(this
.job); } var child = new Parent(); child.showJob(); //'teacher'

很明顯,這個例子中的child獲得屬性job和一個方法showJob,為什麼會獲得呢? 這時候來看看new Parent()到底做了什麼

   var obj = { };   //obj獲得Parent的this引用
   obj.job = 'teacher';   
   obj.__proto__ = Parent.prototype;
   var child = obj;

所以為什麼child獲得了屬性job,是因為他執行了建構函式,child物件上獲得了屬性job。而為什麼child獲得了方法showJob, 是因為物件上有一個隱藏的原型__proto__ ,它指向了Parent.prototype。當我們在物件child上呼叫方法時,它首先檢查物件自己是否具有這個方法,沒有的話搜尋自己的隱藏原型中有沒有這個方法,所以當一個物件上有方法,它要麼存在於自身中,要麼存在於隱式原型中,要實現原型長鏈的繼承,只能從隱式原型上想辦法。
所以,原型繼承是什麼,它的本質是改變其他物件的__proto__,或者說讓它豐富起來,以獲得父建構函式或者祖先建構函式的方法,請看程式碼。

   function Parent(){
   }
   Parent.prototype.showJob = function(){}
   function Child(){
   }
   Child.prototype = new Parent();
   Child.prototype.constructor = Child;
   var grandChild = new Child();

這時候grandChild獲得了showJob方法,因為它的__proto__中有showJob方法,而為什麼它的隱式原型中有這個方法呢,因為new Child()中grandChild.__proto__指向了Child的prototype, 至於為什麼Child的prototype中有showJob方法,因為Child.prototype.__proto__等於Parent.prototype.
至於為什麼有這麼一句

  Child.prototype.constructor = Child;

這是因為原本Child.prototype中有一個constructor屬性指向Child本身,當執行Child.prototype = new Parent()的時候,Child.prototype.constructor指向了Parent,否則下一次new Child的時候,constructor的指向就會不正確,當然,這個在實際開發中即時漏掉也不會有大問題,因為我們很少會對constructor進行讀寫。
以上程式碼還有另一個問題,為什麼我們要把showJob這個方法寫在Parent.prototype上呢,如果寫成如下

    function Parent(){
        this.showJob = function(){}
    }
    function Child(){
    }
    Child.prototype = new Parent();

當然這樣寫也可以,child.prototype物件上有了showJob方法,而不是child.prototype.__proto__,這對於我們原型鏈的繼承並沒有影響。然而這樣寫的方法一多,child.prototype物件上的方法就越多,如果new了多次的話,在記憶體上會比寫在原型上多一些消耗。

那麼在實際開發中,會怎麼實現面向物件的原型繼承呢。正常在我們拿到需求的時候,如果需求邏輯複雜,且在多個頁面中有相似邏輯的時候,我們就會想到使用面向物件了,因為面向物件解決的就是邏輯的封裝和複用。
假設頁面A,頁面B,頁面C中存在相同邏輯,那麼我們可以封裝父建構函式物件

   function Parent(){}
   Parent.prototype = {
      method1: function(){},
      method2: function(){},
      ...
   }
   function A(){      //頁面A
      this.A = A;
   }
   A.prototype = new Parent();
   A.prototype.otherMethod = function(){};

   var a = new A();   //使用物件
   a.init...

首先將頁面A,頁面B,頁面C中相同邏輯抽離,相同邏輯可以是同一個ajax請求返回資料,或者是資料格式化等等的相同操作。將這些方法在Parent.prototype中定義,至於A,B,C頁面自己特有的方法,則在如A.prototype中定義。這樣很好地瞭解決了我們的問題,邏輯清晰,程式碼複用性強。
如果在Parent方法引數中加入了回撥callback,並且在callback中想呼叫子函式方法或者屬性,可以參考我另一篇博文call和apply上手分析

hasOwnProperty

hasOwnProperty方法可以檢測一個屬性是存在於例項中,還是存在於原型中。

   function Parent(){
      this.name = 'sysyzhyupeng';
   }
   Parent.prototype.job = 'teacher';
   Parent.prototype.showJob = function(){
   }
   var parent = new Parent();
   parent.hasOwnProperty('name');  // true
   parent.hasOwnProperty('job');  // false
   //方法也可以
   parent.hasOwnProperty('showJob');  // false 

in

in運算子和hasOwnProperty不同,只要存在在原型上或者物件上就返回true

   function Parent(){
      this.name = 'sysyzhyupeng';
   }
   Parent.prototype.job = 'teacher';
   Parent.prototype.showJob = function(){
   }
   var parent = new Parent();
   'name' in parent;  // true
   'job' in parent  // true
   for(_key in parent){
      console.log(_key);  // 'name', 'job', 'showJob'
   }

在使用for-in迴圈時,返回的是所有能通過物件訪問的且可列舉的屬性。所有開發人正常定義的屬性都是可列舉的,只有在IE8及更早版本除外。

Object.keys

ES5的Object.keys方法可以返回物件上的所有可列舉屬性(注意只有物件上的,從原型上繼承的沒有)