javascript原型和原型鏈詳解

admin發表於2017-04-05

JavaScript不包含傳統的類繼承模型,而是使用prototypal原型模型。

雖然這經常被當作是 JavaScript 的缺點被提及,其實基於原型的繼承模型比傳統的類繼承還要強大。實現傳統的類繼承模型是很簡單,但是實現 JavaScript 中的原型繼承則要困難的多。

由於 JavaScript 是唯一一個被廣泛使用的基於原型繼承的語言,所以理解兩種繼承模式的差異是需要一定時間的,今天我們就來了解一下原型和原型鏈。

剛學習JavaScript的時候,一般都是用如下方式來寫程式碼:

[JavaScript] 純文字檢視 複製程式碼
var decimalDigits = 2,
     tax = 5;
function add(x, y) {
  return x + y;
}
function subtract(x, y) {
  return x - y;
}
//alert(add(1, 3));

通過執行各個function來得到結果,學習了原型之後,我們可以使用如下方式來美化一下程式碼。

原型使用方式一:

在使用原型之前,我們需要先將程式碼做一下小修改:

[JavaScript] 純文字檢視 複製程式碼
var Calculator = function (decimalDigits, tax) {
  this.decimalDigits = decimalDigits;
  this.tax = tax;
};

然後,通過給Calculator物件的prototype屬性賦值物件字面量來設定Calculator物件的原型。

[JavaScript] 純文字檢視 複製程式碼
Calculator.prototype = {
  add: function (x, y) {
    return x + y;
  },
  subtract: function (x, y) {
    return x - y;
  }
};
//alert((new Calculator()).add(1, 3));

這樣,我們就可以new Calculator物件以後,就可以呼叫add方法來計算結果了。

原型使用方式二:

第二種方式是,在賦值原型prototype的時候使用function立即執行的表示式來賦值,即如下格式:

[JavaScript] 純文字檢視 複製程式碼
Calculator.prototype = function () { } ();

它的好處在前面的帖子裡已經知道了,就是可以封裝私有的function,通過return的形式暴露出簡單的使用名稱,以達到public/private的效果,修改後的程式碼如下:

[JavaScript] 純文字檢視 複製程式碼
Calculator.prototype = function () {
  add = function (x, y) {
    return x + y;
  },
 
  subtract = function (x, y) {
    return x - y;
  }
  return {
    add: add,
    subtract: subtract
  }
} ();
//alert((new Calculator()).add(11, 3));

同樣的方式,我們可以new Calculator物件以後呼叫add方法來計算結果了。

分步宣告:

上述使用原型的時候,有一個限制就是一次性設定了原型物件,我們再來說一下如何分來設定原型的每個屬性吧。

[JavaScript] 純文字檢視 複製程式碼
var BaseCalculator = function () {
    //為每個例項都宣告一個小數位數
    this.decimalDigits = 2;
};
         
//使用原型給BaseCalculator擴充套件2個物件方法
BaseCalculator.prototype.add = function (x, y) {
    return x + y;
};
 
BaseCalculator.prototype.subtract = function (x, y) {
    return x - y;
};

首先,宣告瞭一個BaseCalculator物件,建構函式裡會初始化一個小數位數的屬性decimalDigits,然後通過原型屬性設定2個function,分別是add(x,y)和subtract(x,y),當然你也可以使用前面提到的2種方式的任何一種,我們的主要目的是看如何將BaseCalculator物件設定到真正的Calculator的原型上。

[JavaScript] 純文字檢視 複製程式碼
var BaseCalculator = function() {
    this.decimalDigits = 2;
};
 
BaseCalculator.prototype = {
    add: function(x, y) {
        return x + y;
    },
    subtract: function(x, y) {
        return x - y;
    }
};

建立完上述程式碼以後,我們來開始:

[JavaScript] 純文字檢視 複製程式碼
var Calculator = function () {
    //為每個例項都宣告一個稅收數字
    this.tax = 5;
};
Calculator.prototype = new BaseCalculator();

我們可以看到Calculator的原型是指向到BaseCalculator的一個例項上,目的是讓Calculator整合它的add(x,y)和subtract(x,y)這2個function,還有一點要說的是,由於它的原型是BaseCalculator的一個例項,所以不管你建立多少個Calculator物件例項,他們的原型指向的都是同一個例項。

[JavaScript] 純文字檢視 複製程式碼
var calc = new Calculator();
alert(calc.add(1, 1));
//BaseCalculator 裡宣告的decimalDigits屬性,在 Calculator裡是可以訪問到的
alert(calc.decimalDigits);

上面的程式碼,執行以後,我們可以看到因為Calculator的原型是指向BaseCalculator的例項上的,所以可以訪問他的decimalDigits屬性值,那如果我不想讓Calculator訪問BaseCalculator的建構函式裡宣告的屬性值,那怎麼辦呢?這麼辦:

[JavaScript] 純文字檢視 複製程式碼
var Calculator = function () {
    this.tax= 5;
};
Calculator.prototype = BaseCalculator.prototype;

通過將BaseCalculator的原型賦給Calculator的原型,這樣你在Calculator的例項上就訪問不到那個decimalDigits值了,如果你訪問如下程式碼,那將會提升出錯。

[JavaScript] 純文字檢視 複製程式碼
var calc = new Calculator();
alert(calc.add(1, 1));
alert(calc.decimalDigits);

重寫原型:

在使用第三方JS類庫的時候,往往有時候他們定義的原型方法是不能滿足我們的需要,但是又離不開這個類庫,所以這時候我們就需要重寫他們的原型中的一個或者多個屬性或function,我們可以通過繼續宣告的同樣的add程式碼的形式來達到覆蓋重寫前面的add功能,程式碼如下:

[JavaScript] 純文字檢視 複製程式碼
//覆蓋前面Calculator的add() function 
Calculator.prototype.add = function (x, y) {
    return x + y + this.tax;
};
 
var calc = new Calculator();
alert(calc.add(1, 1));

這樣,我們計算得出的結果就比原來多出了一個tax的值,但是有一點需要注意:那就是重寫的程式碼需要放在最後,這樣才能覆蓋前面的程式碼。

原型鏈:

在將原型鏈之前,我們先上一段程式碼:

[JavaScript] 純文字檢視 複製程式碼
function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};
 
function Bar() {}
 
// 設定Bar的prototype屬性為Foo的例項物件
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
 
// 修正Bar.prototype.constructor為Bar本身
Bar.prototype.constructor = Bar;
 
var test = new Bar() // 建立Bar的一個新例項
 
// 原型鏈
test [Bar的例項]
    Bar.prototype [Foo的例項] 
        { foo: 'Hello World' }
        Foo.prototype
            {method: ...};
            Object.prototype
                {toString: ... /* etc. */};

上面的例子中,test 物件從 Bar.prototype 和 Foo.prototype 繼承下來;因此,它能訪問 Foo 的原型方法 method。同時,它也能夠訪問那個定義在原型上的 Foo 例項屬性 value。需要注意的是 new Bar() 不會創造出一個新的 Foo 例項,而是重複使用它原型上的那個例項;因此,所有的 Bar 例項都會共享相同的 value 屬性。

相關文章