標籤:覆蓋 限制 避免 round 原型 設定 自己 繼承 擷取
// foo 變數是上例中的for(var i in foo) { if (foo.hasOwnProperty(i)) { console.log(i); }}
JavaScript 不包括傳統的類繼承模型,而是使用 prototypal 原型模型。
儘管這常常被當作是 JavaScript 的缺點被提及,事實上基於原型的繼承模型比傳統的類繼承還要強大。實現傳統的類繼承模型是非常easy,可是實現 JavaScript 中的原型繼承則要困難的多。
因為 JavaScript 是唯一一個被廣泛使用的基於原型繼承的語言,所以理解兩種繼承模式的差異是須要一定時間的,今天我們就來瞭解一下原型和原型鏈。
原型
10年前。我剛學習JavaScript的時候,一般都是用例如以下方式來寫代碼:
<pre name="code" class="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來得到結果,學習了原型之後。我們能夠使用例如以下方式來美化一下代碼。
原型使用方式1
在使用原型之前。我們須要先將代碼做一下小改動:
var Calculator = function (decimalDigits, tax) { this.decimalDigits = decimalDigits; this.tax = tax;};
然後。通過給Calculator對象的prototype屬性賦值對象字面量來設定Calculator對象的原型。
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方法來計算結果了。
原型使用方式2
另外一種方式是,在賦值原型prototype的時候使用function馬上啟動並執行運算式來賦值,即例如以下格式:
Calculator.prototype = function () { } ();它的優點在前面的文章裡已經知道了。就是能夠封裝私人的function,通過return的形式暴露出簡單的使用名稱,以達到public/private的效果。改動後的代碼例如以下:
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方法來計算結果了。分步聲明
上述使用原型的時候。有一個限制就是一次性設定了原型對象。我們再來說一下怎樣分來設定原型的每一個屬性吧。
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的原型上。
var BaseCalculator = function() { this.decimalDigits = 2;};BaseCalculator.prototype = { add: function(x, y) { return x + y; }, subtract: function(x, y) { return x - y; }};
建立完上述代碼以後,我們來開始:
var Calculator = function () { //為每一個執行個體都聲明一個稅收數字 this.tax = 5;}; Calculator.prototype = new BaseCalculator();
我們能夠看到Calculator的原型是指向到BaseCalculator的一個執行個體上,目的是讓Calculator整合它的add(x,y)和subtract(x,y)這2個function,另一點要說的是,因為它的原型是BaseCalculator的一個執行個體,所以無論你建立多少個Calculator對象執行個體,他們的原型指向的都是同一個執行個體。
var calc = new Calculator();alert(calc.add(1, 1));//BaseCalculator 裡聲明的decimalDigits屬性,在 Calculator裡是能夠訪問到的alert(calc.decimalDigits);
上面的代碼。執行以後。我們能夠看到由於Calculator的原型是指向BaseCalculator的執行個體上的,所以能夠訪問他的decimalDigits屬性值,那假設我不想讓Calculator訪問BaseCalculator的建構函式裡聲明的屬性值,那怎麼辦呢?這麼辦:
var Calculator = function () { this.tax= 5;};Calculator.prototype = BaseCalculator.prototype;
通過將BaseCalculator的原型賦給Calculator的原型,這樣你在Calculator的執行個體上就訪問不到那個decimalDigits值了,假設你訪問例如以下代碼,那將會提升出錯。
var calc = new Calculator();alert(calc.add(1, 1));alert(calc.decimalDigits);
重寫原型
在使用第三方JS類庫的時候,往往有時候他們定義的原型方法是不能滿足我們的須要。可是又離不開這個類庫。所以這時候我們就須要重寫他們的原型中的一個或者多個屬性或function。我們能夠通過繼續聲明的相同的add代碼的形式來達到覆蓋重寫前面的add功能,代碼例如以下:
//覆蓋前面Calculator的add() function Calculator.prototype.add = function (x, y) { return x + y + this.tax;};var calc = new Calculator();alert(calc.add(1, 1));
這樣。我們計算得出的結果就比原來多出了一個tax的值。可是有一點須要注意:那就是重寫的代碼須要放在最後,這樣才幹覆蓋前面的代碼。
原型鏈
在將原型鏈之前,我們先上一段代碼:
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 屬性。
屬性尋找
當尋找一個對象的屬性時。JavaScript 會向上遍曆原型鏈。直到找到給定名稱的屬性為止,到尋找到達原型鏈的頂部 - 也就是 Object.prototype - 可是仍然沒有找到指定的屬性,就會返回 undefined,我們來看一個範例:
function foo() { this.add = function (x, y) { return x + y; }}foo.prototype.add = function (x, y) { return x + y + 10;}Object.prototype.subtract = function (x, y) { return x - y;}var f = new foo();alert(f.add(1, 2)); //結果是3。而不是13alert(f.subtract(1, 2)); //結果是-1
通過代碼執行。我們發現subtract是安裝我們所說的向上尋找來得到結果的,可是add方式有點小不同。這也是我想強調的,就是屬性在尋找的時候是先尋找自身的屬性,假設沒有再尋找原型。再沒有,再往上走。一直插到Object的原型上。所以在某種層面上說。用 for in語句遍曆屬性的時候,效率也是個問題。
另一點我們須要注意的是,我們能夠賦值不論什麼類型的對象到原型上,可是不能賦值原子類型的值。比方例如以下代碼是無效的:
function Foo() {}Foo.prototype = 1; // 無效
hasOwnProperty函數
hasOwnProperty是Object.prototype的一個方法。它但是個好東西,他能推斷一個對象是否包括自己定義屬性而不是原型鏈上的屬性,由於hasOwnProperty 是 JavaScript 中唯一一個處理屬性但是不尋找原型鏈的函數。
// 改動Object.prototypeObject.prototype.bar = 1; var foo = {goo: undefined};foo.bar; // 1‘bar‘ in foo; // truefoo.hasOwnProperty(‘bar‘); // falsefoo.hasOwnProperty(‘goo‘); // true
僅僅有 hasOwnProperty 能夠給出正確和期望的結果,這在遍曆對象的屬性時會非常實用。 沒有其他方法能夠用來排除原型鏈上的屬性,而不是定義在對象自身上的屬性。
但有個噁心的地方是:JavaScript 不會保護 hasOwnProperty 被非法佔用,因此假設一個對象碰巧存在這個屬性,就須要使用外部的 hasOwnProperty 函數來擷取正確的結果。
var foo = { hasOwnProperty: function() { return false; }, bar: ‘Here be dragons‘};foo.hasOwnProperty(‘bar‘); // 總是返回 false// 使用{}對象的 hasOwnProperty,並將其上下為設定為foo{}.hasOwnProperty.call(foo, ‘bar‘); // true
當檢查對象上某個屬性是否存在時,hasOwnProperty 是唯一可用的方法。同一時候在使用 for in loop 遍曆對象時。推薦總是使用 hasOwnProperty 方法。這將會避免原型對象擴充帶來的幹擾,我們來看一下範例:
// 改動 Object.prototypeObject.prototype.bar = 1;var foo = {moo: 2};for(var i in foo) { console.log(i); // 輸出兩個屬性:bar 和 moo}
我們沒辦法改變for in語句的行為,所以想過濾結果就僅僅能使用hasOwnProperty 方法。代碼例如以下:
這個版本號碼的代碼是唯一正確的寫法。因為我們使用了 hasOwnProperty,所以這次僅僅輸出 moo。
假設不使用 hasOwnProperty,則這段代碼在原生對象原型(比方 Object.prototype)被擴充時可能會出錯。
總結:推薦使用 hasOwnProperty。不要對代碼執行的環境做不論什麼如果,不要如果原生對象是否已經被擴充了。
JavaScript探秘:強大的原型和原型鏈