深入理解JavaScript系列(5):強大的原型和原型鏈
JavaScript 不包含傳統的類繼承模型,而是使用 prototypal 原型模型。
雖然這經常被當作是 JavaScript 的缺點被提及,其實基於原型的繼承模型比傳統的類繼承還要強大。實現傳統的類繼承模型是很簡單,但是實現 JavaScript 中的原型繼承則要困難的多。
<script>
//【1】base
var BaseCalculator =function () {
//為每個實列都宣告一個小數位數
this.decimalDigits=2;
}
//【2】 使用原型擴充套件兩個物件方法
BaseCalculator.prototype.add=function (x, y) {
return x+y;
}
BaseCalculator.prototype.subtract=function (x, y) {
return x-y;
}
// 【3】定義一個類 (1)讓Calculator整合它的add(x,y)和subtract(x,y)這2個function
Calculator=function () {
//為每個實列都宣告一個整數數字
this.tax=5;
}
// (2)Calculator的原型是BaseCalculator它的實列,所以不管你建立多少個Calculator物件例項,他們的原型指向的都是同一個例項
Calculator.prototype=new BaseCalculator();
var cal=new Calculator();
console.log(cal.add(1,1))
console.log(cal.subtract(2,1))
console.log(cal.tax)
console.log(cal.decimalDigits)
</script>
(3)通過將BaseCalculator的原型賦給Calculator的原型,這樣你在Calculator的例項上就訪問不到那個decimalDigits值了,如果你訪問如下程式碼,那將會提升出錯。
不想讓Calculator訪問BaseCalculator的建構函式裡宣告的屬性值
var Calculator = function () {
this.tax= 5;
};
Calculator.prototype = BaseCalculator.prototype;
重寫原型:
在使用第三方JS類庫的時候,往往有時候他們定義的原型方法是不能滿足我們的需要,但是又離不開這個類庫,所以這時候我們就需要重寫他們的原型中的一個或者多個屬性或function,我們可以通過繼續宣告的同樣的add程式碼的形式來達到覆蓋重寫前面的add功能,程式碼如下:
// 重寫原型:
Calculator.prototype.add=function (x, y) {
return x+y+this.tax;
}
var calc=new Calculator();
console.log(calc.add(1,1))
這樣,我們計算得出的結果就比原來多出了一個tax的值,但是有一點需要注意:那就是重寫的程式碼需要放在最後,這樣才能覆蓋前面的程式碼。
原型鏈
function Foo() {
this.value = 24;
}
Foo.prototype = {
method: function () {
console.log('Foo method')
}
}
function Bar() {
}
Bar.prototype = new Foo();//【1】bar原型為Foo
Bar.prototype.foo = 'hello world';
//修正bar.prototype.constructor為bar本身
Bar.prototype.constructor = Bar;
var test = new Bar();
//原型鏈
test.method();
console.log(test.value += 6);
console.log(test.value);
console.log(test.foo)
console.log('------------------------test for in------------------')
for (var i in test) {
console.log(test[i])
}
上面的例子中,test 物件從 Bar.prototype 和 Foo.prototype 繼承下來;因此,它能訪問 Foo 的原型方法 method。同時,它也能夠訪問那個定義在原型上的 Foo 例項屬性 value。需要注意的是 new Bar() 不會創造出一個新的 Foo 例項,而是重複使用它原型上的那個例項;因此,所有的 Bar 例項都會共享相同的 value 屬性
屬性查詢:
當查詢一個物件的屬性時,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查詢到達原型鏈的頂部 - 也就是 Object.prototype - 但是仍然沒有找到指定的屬性,就會返回 undefined
//屬性查詢
console.log('-------------------------------屬性查詢---------------------')
function foo1() {
this.add=function (x, y) {
return x+y;
}
}
foo1.prototype.add=function (x, y) {
return x+y+10;
}
Object.prototype.subtract=function (x, y) {
return x-y;
}
var fo1=new foo1();
var a=fo1.add(1,2)
console.log(a)
通過程式碼執行,我們發現subtract是安裝我們所說的向上查詢來得到結果的,但是add方式有點小不同,這也是我想強調的,就是屬性在查詢的時候是先查詢自身的屬性,如果沒有再查詢原型,再沒有,再往上走,一直插到Object的原型上,所以在某種層面上說,用 for in語句遍歷屬性的時候,效率也是個問題
還有一點我們需要注意的是,我們可以賦值任何型別的物件到原型上,但是不能賦值原子型別的值,比如如下程式碼是無效的:
hasOwnProperty函式:
hasOwnProperty是Object.prototype的一個方法
他能判斷一個物件是否包含 自定義屬性 而不是原型鏈上的屬性
JavaScript 中唯一一個處理屬性但是不查詢原型鏈的函式。
Object.prototype.bar = 11;
var foo = {goo: undefined};
console.log(foo.bar)//11
console.log('bar' in foo)//true
console.log(foo.hasOwnProperty('bar'))////false
console.log(foo.hasOwnProperty('goo'))//true
只有 hasOwnProperty 可以給出正確和期望的結果,這在遍歷物件的屬性時會很有用。 沒有其它方法可以用來排除原型鏈上的屬性,而不是定義在物件自身上的屬性。
但有個噁心的地方是:JavaScript 不會保護 hasOwnProperty 被非法佔用,因此如果一個物件碰巧存在這個屬性,就需要使用外部的 hasOwnProperty 函式來獲取正確的結果。
console.log('------------------不被保護的 hasOwnProperty----------------')
var foo = {
hasOwnProperty: function () {
return false;
}, bar: 'Here be dragons'
};
var bl=foo.hasOwnProperty('bar');
console.log(bl);
console.log('------------------解決方法----------------');
// 使用{}物件的 hasOwnProperty,並將其上下為設定為foo
var bl1= ( {}).hasOwnProperty.call(foo, 'bar'); // true
console.log(bl1)
當檢查物件上某個屬性是否存在時,hasOwnProperty 是唯一可用的方法。同時在使用 for in loop 遍歷物件時,推薦總是使用 hasOwnProperty 方法,這將會避免原型物件擴充套件帶來的干擾,我們來看一下例子: