Javascript: 從prototype漫談到繼承(2)

本文同時也發表在我另外一篇獨立博客 《Javascript: 從prototype漫談到繼承(2)》(管理員請注意!這兩個都是我本身的原創博客!不要踢出首頁!不是轉載!已經誤會三次了!)javascript

上一篇漫談繼承的結尾咱們得出了第一個比較完美的解決方案:java

function extend(Child, Parent) {
  var F = function(){};
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  // 一旦重置了函數的prototype,須要從新賦值prototype.constructor,
  // 忽略這方面的介紹
  Child.prototype.constructor = Child;
  // 保留對父類的引用,
  // 忽略對這方面的介紹
  Child.uber = Parent.prototype;
}

 

這個方法的「比較完美」之處就在於引入了一箇中間變量var F = function(){}。由於若是直接讓子類的prototype直接繼承自父類的話Child.prototype = Parent.prototype;,出因而淺拷貝,可能對子類prototype某個屬性的修改會影響到父類。而是用了一箇中間變量則能避免這樣的狀況發生,具體請參看上一篇《Javascript: 從prototype漫談到繼承(1)》jquery

再讓咱們換一種思路,繼承的本質是把本身能子類能訪問父類的屬性和方法,那麼也能夠one by one的把父類的屬性所有拷貝過來,像git

function extend2(Child, Parent) {
  var p = Parent.prototype;
  var c = Child.prototype;
  for (var i in p) {
     c[i] = p[i];
  }
  c.uber = p;
}

 

這個方法的不是那麼的優雅,由於明明是能夠引用父類的屬性結果又都拷貝了一份。可是儘量的減小了引用,減小了查找的次數。github

從開始談繼承到如今咱們的聊的都是類與類,或者說構造函數與構造函數之間的繼承,但這裏是javascript,咱們還須要考慮已經實例化了的對象之間的繼承,把上面的extend2稍稍改造一下就是了數組

function extendCopy(p) {
  var c = {};  
  for (var i in p) {
     c[i] = p[i];
  }
  c.uber = p;
  return c;
}

 

在使用這個方法的時候,傳入的參數就不在是一個構造函數,而是一個實實在在的對象。這是jquery的$.extend繼承方法最基本的思想ide

 

上面的方法,或者說以上的全部方法,都沒有解決一個深淺拷貝的問題。以上的全部方法使用的都是淺拷貝(shallow copy),你拷貝的僅僅是指向原對象內存地址的指針而已,若是你修改子類繼承自父類的某個屬性,極可能父類的某個屬性也被修改了。好比:函數

var c = {};
var p = {
    pro: [1, 2, 3]
}

c.pro = p.pro
c.pro.push(4)

console.log(p.pro) //[1,2,3,4]

 

在上面的例子中c的pro屬性繼承自p.pro,pro是一個數組類型,可是當你修改c.pro時,p的pro也被修改了,這就是淺拷貝的危害!除去5種基本數據類型(Number, String, Boolean, Null, Undefined)之外的數據類型的拷貝都會是淺拷貝,可是這種危險仍是要依據對子類屬性的操做方式而定。好比當子類繼承自父類的某個屬性時object對象:this

var c = {};
var p = {
    name: "lee"
}

c = p;
c = "kill"

console.log(p) 

 

若是你把整個屬性從新賦值,父類屬性是不會被修改的。其中的原理是,繼承的時候你是子類和父類該屬性的指針都指向同一內存區域,這樣的修改只是修改了子類該屬性的指針,指向另外一塊地方去了,可是你按下面的方式修改就有問題了spa

var c = {};
var p = {
    name: "lee"
}

c = p;
c.name = "wang"

console.log(p)  // wang

 

沒錯,這種狀況下父類也被修改了,由於此時子類和父類的該屬性還在用同一塊內存區域。

解決這個問題的方法也很簡單,當咱們沿用上面extendCopy方法,可是在拷貝每個屬性時,咱們都去檢查它是否須要深拷貝,若是須要,則進行深拷貝,這就是jquery的方法,只不過它作了更多更嚴密的判斷,好比判斷object類型和array類型的時候:

function deepCopy(p, c) {
  var c = c || {}; 
  for (var i in p) {
     if (typeof p[i] === 'object') {
      c[i] = (p[i].constructor === Array) ? [] : {};
      deepCopy(p[i], c[i]);
     } else {
      c[i] = p[i];
     } 
  }
  return c;
}

 

在實戰中咱們除了繼承外還想再生產這個對象的時候添加一些本身的屬性,那麼咱們就能夠同時用到prototype繼承和一對一的拷貝屬性

function objectPlus(o, stuff) {
  var n;
  function F() {}
  F.prototype = o;
  n = new F(); 
  // 繼承父類屬性完畢
  n.uber = o;
  // 繼承自定義屬性
  for (var i in stuff) {
     n[i] = stuff[i];
  }
  return n;
}

 

最後來看看大神道格拉斯的(Douglas Crockford)提出的解決方案。

首先他提出了一個Object()方法:

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

 

其實這個方法與咱們的第一個,本文開頭的extend方法相似,只不過咱們的方法指定了要繼承的子類

假設咱們有一個var twoD = {name: "2d shape"}須要被繼承,利用object他的解決方法是

  1. 利用object方法,把twoD克隆至一個that對象中,
  2. 給that對象添加本身的屬性
  3. 返回that
function triangle(s, h) {
  var that = object(twoD);
  that.name ='Triangle';
  that.getArea = function(){return this.side * this.height / 2;};
  that.side = s;
  that.height = h;
  return that;
}

 

留意到triangle只是一個普通的函數,而不是一個構造函數,傳入的參數是你自定義化的值

 

基本上介紹完了,其實還一個借用構造函數來實現繼承的方法,但我的以爲,在上面那麼多方法的陪襯下,這個方法顯得不清晰,比較晦澀一些,上面方法以及足夠用了。就不予介紹了。

最後原本想貼一段jquery的extend源碼讓大家好好感覺一下的,可是代碼有點長有點寬,有興趣的同窗能夠去github的jquery源碼裏瞧瞧,在/src/core.js文件中

 

最後吐槽一句,其實繼承的本質咱們是但願能實現如下功能

  • 父類有的我都有,我也能重載,但不至於影響到父類的屬性和方法
  • 除了繼承以外,我也能添加本身的方法和屬性

能作到以上兩點,夫復何求呢!介紹了這麼多方法,只是那你有一個瞭解,都不是完美的,你徹底能夠都參考一遍而後組合一個適合本身業務邏輯的。行動吧騷年!

相關文章
相關標籤/搜索