[TOC] JavaScript的 對象模型 非常強大,但它與標準面向對象語言的對象模型稍有不同。JavaScript採用的不是基於類的面向對象系統,而是更強大的 原型 模型,其中的對象可繼承和擴展其他對象的行為。 JavaScript沒有傳統的面向對象模型,即從類創建對象的模型。事實上,JavaS ...
目錄
JavaScript的對象模型非常強大,但它與標準面向對象語言的對象模型稍有不同。JavaScript採用的不是基於類的面向對象系統,而是更強大的原型模型,其中的對象可繼承和擴展其他對象的行為。
JavaScript沒有傳統的面向對象模型,即從類創建對象的模型。事實上,JavaScript根本就沒有類。在JavaScript中,對象從其他對象那裡繼承行為,我們稱之為原型式繼承(prototypal inheritance)或基於原型的繼承。
tips: 這在ES6版本中發生變化: ES6中添加了類的概念。
再談構造函數
我們來一看一下如下的構造函數:
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
this.bark = function () {
if (this.weight > 25) {
alert(this.name + " says Woof!");
} else {
alert(this.name + " says Yip!");
}
};
}
通過使用這個構造函數,可創建一致的小狗對象,並根據喜好進行定製;還可利用這個構造函數中定義的方法(這裡只有一個bark)。另外,每個小狗對象都從構造函數那裡獲得了相同的代碼,未來需要修改代碼時,這可避免很多麻煩。這很好,但在運行階段執行下麵的代碼時,情況將如何呢?
var fido = new Dog("Fido", "Mixed", 38);
var fluffy = new Dog("Fluffy", "Poodle", 30);
var spot = new Dog("spot", "chihuahua", 10);
這些代碼創建三個小狗對象。使用新對象圖表示時,這些對象類似於下麵這樣。
從圖中可以看出每個對象都有一個bark方法。
重覆的方法真是個問題嗎?
確實是個問題。一般而言,我們不希望每次使用構造函數實例化一個對象時,都創建一組新的方法。這樣會影響應用程式的性能,占用電腦資源。這可能是個大問題,在移動設備上尤其如此。你將看到,還有更靈活、更強大的JavaScript對象創建方式。
我們回過頭去想想使用構造函數的主要目的:試圖重用行為。例如,創建大量的小狗對象時,我們希望這些對象都使用相同的bark方法。通過使用構造函數,我們只需將bark方法放在構造函數Dog中,這樣每次實例化對象時,都將重用方法bark的代碼,從而在代碼層面實現重用行為的目的。但在運行階段,這種解決方案的效果並不好,因為每個小狗對象都將獲得自己的bark方法副本。
為何會出現這種問題呢?這是因為我們沒有充分利用JavaScript的對象模型。JavaScript對象模型基於原型的概念,在這種模型中,可通過擴展其他對象(即原型對象)來創建對象。
為演示原型式繼承,下麵首先來創建小狗原型。
原型是什麼?
JavaScript對象可從其他對象那裡繼承屬性和行為。更具體地說,JavaScript使用原型式繼承,其中其行為被繼承的對象稱為原型。這旨在繼承既有屬性(包括方法),同時在新對象中添加屬性。(對象繼承另一個對象後,便可訪問其所有方法和屬性。)
來看一個示例:
我們從用於創建小狗對象的原型開始,它可能類似於下麵這樣。
有了不錯的小狗原型後,便可創建從該原型繼承屬性的小狗對象了。對於這些小狗對象,還可根據其具體需求添加屬性和行為。例如,對於每個小狗對象,我們都將添加屬性name、breed和weight。
這些小狗對象需要發出叫聲、奔跑或搖尾巴時,都可使用原型提供的這些行為,因為它們從原型那裡繼承了這些行為。為了讓你明白其中的工作原理,下麵來創建幾個小狗對象。
繼承原型
首先,需要創建小狗對象Fido、Fluffy和Spot的對象圖,讓它們繼承新創建的小狗原型。為表示繼承關係,我們將繪製從小狗實例到原型的虛線。別忘了,我們只將所有小狗都需要的方法和屬性放在小狗原型中,因為所有小狗都將繼承它們。對於所有隨小狗對象而異的屬性,如name,我們都將其都放在小狗實例中,因為每條小狗的這些屬性都各不相同。
繼承的工作原理
既然方法bark並不包含在各個小狗對象中,而是包含在原型中,如何讓小狗發出叫聲呢?這正是繼承的用武之地。對對象調用方法時,如果在對象中找不到,將在原型中查找它,如下所示。
屬性的情況也一樣。如果我們編寫了需要獲取fido.name的代碼,將從fido對象中獲取這個值。如果要獲取fido.species的值,將首先在對象fido中查找;在這裡找不到後,將接著在小狗原型中查找(結果是找到了)。
建立了這種繼承關係,我們就可以創建大量的小狗對象,而且這些小狗對象在運行階段使用同一個bark方法,從而避免了龐大的運行階段開銷。如下圖所示:
明白如何使用繼承後,便可以創建大量的小狗了。這些小狗都能發出叫聲,但依賴於小狗原型提供的方法bark。
我們實現了代碼重用:不僅只需在一個地方編寫代碼,而且讓所有小狗實例都在運行階段使用同一個bark方法,從
而避免了龐大的運行階段開銷。
你將看到,通過使用原型,可快速地創建對象,這些對象不僅能夠重用代碼,還能新增行為和屬性。
重寫原型
繼承原型並不意味著必須與它完全相同。在任何情況 下,都可重寫原型的屬性和方法,為此只需在對象實例 中提供它們即可。這之所以可行,是因為JavaScript總 是先在對象實例(即具體的小狗對象)中查找屬性;如 果找不到,再在原型中查找。因此,要為對象spot定 制方法bark,只需在其中包含自定義的方法bark。這 樣,JavaScript查找方法bark以便調用它時,將在對象 spot中找到它,而不用勞神去原型中查找。
下麵來看看如何在對象spot中重寫方法bark,讓它發出叫聲時顯示says WOOF!。
原型從哪裡來
前面花了很多篇幅討論小狗原型,你現在可能想看的是代碼示例,而不是對象圖示例。那麼,如何創建或獲取小狗原型呢?實際上,你已經有了一個這樣的原型,只是你沒有意識到而已。
下麵演示瞭如何在代碼中訪問這個原型:
Dog.prototype //如果你查看構造函數Dog,將發現它有一個 prototype屬性。這是一個指向原型的引用。
等等!!!
Dog是個構造函數,即函數。你是說它有屬性嗎?
是的,在JavaScript中,函數也是對象。實際上,在JavaScript中,幾乎所有的東西都是對象,數組也
是——你可能還沒有意識到這一點。
如何設置原型?
前面說過,可通過構造函數Dog的屬性prototype來訪問原型對象,但這個原型對象包含哪些屬性和方法呢?預設包含的不多。換句話說,你需要給原型添加屬性和方法,這通常是在使用構造函數前進行的。
下麵來設置小狗原型。為此,得有一個可供使用的構造函數。下麵來看看如何根據對象圖創建這樣的構造函數:
創建構造函數後,便可以設置小狗原型了。我們希望它包含屬性species以及方法bark、run和wag,如下所示:
Dog.prototype.species = "Canine"; //將字元串"Canine"賦給原型的屬性species。
Dog.prototype.bark = function () {
if (this.weight > 25) {
console.log(this.name + " says Woof!");
} else {
console.log(this.name + " says Yip!");
}
};
Dog.prototype.run = function () {
console.log("Run!");
};
Dog.prototype.wag = function () {
console.log("Wag!");
};
創建幾個小狗對象並對原型進行測試
為測試這個原型,請在一個文件(index.html)中輸入下麵的代碼,再在瀏覽器(chrome)中載入它。這裡再次列出了前一頁的代碼,並添加了一些測試代碼。請確保所有的小狗對象都像預期的那樣發出叫聲、奔跑和搖尾。
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
Dog.prototype.species = "Canine";
Dog.prototype.bark = function () {
if (this.weight > 25) {
console.log(this.name + " says Woof!");
} else {
console.log(this.name + " says Yip!");
}
};
Dog.prototype.run = function () {
console.log("Run!");
};
Dog.prototype.wag = function () {
console.log("Wag!");
};
var fido = new Dog("Fido", "Mixed", 38);
var fluffy = new Dog("Fluffy", "Poodle", 30);
var spot = new Dog("Spot", "Chihuahua", 10);
fido.bark();
fido.run();
fido.wag();
fluffy.bark();
fluffy.run();
fluffy.wag();
spot.bark();
spot.run();
spot.wag();
運行結果如下圖所示:
編寫讓Spot發出叫聲時顯示says WOOF!的代碼
別擔心,我們可沒忘記Spot。Spot要求在發出叫聲時顯示says WOOF!,因此我們需要重寫原型,給
Spot提供自定義方法bark。下麵來修改代碼:
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
Dog.prototype.species = "Canine";
Dog.prototype.bark = function () {
if (this.weight > 25) {
console.log(this.name + " says Woof!");
} else {
console.log(this.name + " says Yip!");
}
};
Dog.prototype.run = function () {
console.log("Run!");
};
Dog.prototype.wag = function () {
console.log("Wag!");
};
var fido = new Dog("Fido", "Mixed", 38);
var fluffy = new Dog("Fluffy", "Poodle", 30);
var spot = new Dog("Spot", "Chihuahua", 10);
spot.bark = function() { //重寫了原型的bark方法,該方法是spot自定義的
console.log(this.name + " says WOOF!");
};
fido.bark();
fido.run();
fido.wag();
fluffy.bark();
fluffy.run();
fluffy.wag();
spot.bark();
spot.run();
spot.wag();
疑惑:鑒於方法bark位於原型而不是對象中,其中的this.name怎麼不會導致問題呢?
在沒有使用原型的情況下,這很容易解釋,因為this指的是方法被調用的對象。調用原型中的方法bark時,你可能認為this指的是原型對象,但情況並非如此。
調用對象的方法時,this被設置為方法被調用的對象。即便在該對象中沒有找到調用的方法,而是在原型中找到了 它,也不會修改this的值。在任何情況下,this都指向原始對象,即方法被調用的對象,即便該方法位於原型中亦如此。因此,即便方法bark位於原型中, 調用這個方法時,this也將被設置為原始小狗對象,得到的結果也是我們期望的,如顯示Fluffy says Woof!。
原型是動態的
讓所有的小狗學會新技能
該讓所有小狗都學會新技能了。沒錯,就是所有的小狗。使用原型後,如果給原型添加一個方法,所有的小狗對象都將立即從原型那裡繼承這個方法並自動獲得這種新行為,包括添加方法前已創建的小狗對象。
假設我們要讓所有小狗都會坐下,只需在原型中添加一個坐下的方法即可。
var barnaby = new Dog("Barnary", "Basset Hound", 55);
Dog.prototype.sit = function () {
console.log(this.name + " is now sitting!");
};
barnaby.sit();
spot.sit();
Barnaby能夠坐下了,看到這一點我們很高興。實際上,現在所有的小狗都能夠坐下,因為在原型中添加方法後,繼承該原型的任何對象都能使用這個方法。
疑問?
1.也就是說,給原型添加新的方法或屬性後,繼承該原型的所有對象實例都將立即看到它?
答: 如果你說的“看到”是繼承的意思,那你說的完全正確。請註意,這提供了一個途徑,讓你只需在運行階段
修改原型,就可擴展或修改其所有實例的行為。
2.我知道,給原型添加新屬性後,繼承該原型的所有對象都將包含這個屬性,但修改原型的既有屬性呢?這是
否也會影響繼承原型的所有對象?比方說,如果我將屬性species的值從Canine改為Feline,會不會導致所有既
有小狗對象的屬性species都變成Feline?
答:是的。修改原型的任何屬性時,都將影響繼承該原型的所有對象--只要它們沒有重寫這個屬性。
方法sit更有趣的實現
下麵來讓方法sit更有趣些:小狗開始處於非坐著(即站立)狀態。在方法sit中,判斷小狗是否是坐著的。如果不是,就讓它坐著;如果是,就告訴用戶小狗已經是坐著的。為此,需要一個額外的屬性sitting,用於跟蹤小狗是否是坐著的。下麵來編寫這樣的代碼:
這些代碼的有趣之處在於,小狗實例剛創建時,從原型那裡繼承了屬性sitting,該屬性的值預設為false;但調用方法sit後,就給小狗實例添加了屬性sitting的值,導致在小狗實例中創建了屬性sitting。這讓我們能夠給所有小狗對象指定預設值,併在需要時對各個小狗進行定製。
再談屬性sitting的工作原理
下麵來確保你明白了其中的工作原理,因為如果你沒有仔細分析前述實現,可能遺漏重要的細節。要點如下:首次獲取sitting的值時,是從原型中獲取的;但接下來將sitting設置為true時,是在對象實例而不是原型中進行的。在對象實例中添加這個屬性後,接下來每次獲取sitting的值時,都將從對象實例中獲取,因為它重寫了原型中的這個屬性。下麵再次詳細地介紹這一點。
既然說到屬性,在代碼中是否有辦法判斷使用的屬性包含在實例還是原型中呢?
有辦法,可使用每個對象都有的方法hasOwnProperty。如果屬 性是在對象實例中定義的,這個方法將返回true。如果屬性不是 在對象實例中定義的,但能夠訪問它,就可認為它肯定是在原型 中定義的。
下麵來對fido和spot調用這個方法。首先,我們知道,在小狗 原型中定義了屬性species,而且spot和fido都沒有重寫這個屬性。因此,如果我們對這兩個對象調用方法hasOwnProperty, 並以字元串的方式傳入屬性名 species ,結果都將為false:
spot.hasOwnProperty("species");
fido.hasOwnProperty("species");
上面這兩條語句都返回false,因為species是在原型而不是對象實例spot和fido中定義的。
下麵來嘗試對屬性sitting進行這種判斷。我們知道,在原型中定義了屬性sitting,並將其初始化為false,因此將spot.sitting設置為true時,將重寫原型中的屬性sitting,併在實例spot中定義屬性sitting。下麵來詢問spot和fido自己是否定義了屬性sitting:
spot.hasOwnProperty("sitting"); //首次檢查spot是否有自己的sitting屬性時,結果為false
spot.sitting = true; //接下來,我們將spot.sitting設 置為true,這將在實例spot中 添加屬性sitting。
spot.hasOwnProperty("sitting"); //這次調用hasOwnProperty時,結 果為true,因為spot現在有自己 的sitting屬性。
fido.hasOwnProperty("sitting"); //但對fido調用hasOwnProperty時,結果為false,因為實 例fido沒有sitting屬性。這意味著fido使用的sitting屬性 是在原型中定義的,而fido從原型那裡繼承了這個屬性。
建立原型鏈
使用JavaScript時,可以有多個原型。(就像你得到的遺產。你並非只繼承了父母的特質,不是嗎?你還繼承了祖父母、外祖父母、曾祖父母、曾外祖父母等的一些特質。)在JavaScript中,原型還可以繼承原型,可建立供對象繼承的原型鏈。對象不僅可以繼承一個原型的屬性,還可繼承一個原型鏈。基於前面考慮問題的方式,這並不難理解。
假設我們需要一個用於創建表演犬的表演犬原型,並希望這個原型依賴於小狗原型提供的方法bark、run和wag。下麵就來建立這樣的原型鏈,體會一下其中的各個部分是如何協同工作的。
原型鏈中的繼承原理
為表演犬建立原型鏈後,下麵來看看其中的繼承原理。對於本頁下方的每個屬性和方法,請沿原型鏈向上找出它們都是在哪裡定義的。
創建表演犬原型
創建小狗原型時,只需直接使用構造函數Dog的屬性prototype提供的空對象,在其中添加要讓每個小狗實例都繼承的屬性和方法即可。
但創建表演犬原型時,我們必須做更多的工作,因為我們需要的是一個繼承另一個原型(小狗原型)的原型對象。為此,我們必須創建一個繼承小狗原型的對象,再親自動手建立關聯。
當前,我們有一個小狗原型,還有一系列繼承這個原型的小狗實例,而目標是創建一個繼承小狗原型的表演犬原型以及一系列繼承表演犬原型的表演犬實例。
為此,需要一步一步來完成。
首先,需要一個繼承小狗原型的對象
前面說過,表演犬原型是一個繼承小狗原型的對象。要創建繼承小狗原型的對象,最佳方式是什麼呢?其實就是前面創建小狗實例時一直採用的方式。你可能還記得,這種方式類似於下麵這樣:
上述代碼創建一個繼承小狗原型的對象,因為它與以前創建小狗實例時使用的代碼完全相同,只是沒有向構造函數提供任何實參。為什麼這樣做呢?因為在這裡,我們只需要一個繼承小狗原型的小狗對象,而不關心其細節。
當前,我們需要的是一個表演犬原型。與其他小狗實例一樣,它也是一個繼承小狗原型的對象。下麵來看看如何將這個空的小狗實例變成所需的表演犬原型。
接下來,將新建的小狗實例變成表演犬原型
至此,我們有了一個小狗實例,但如何使其成為表演犬原型呢?為此,只需將它賦給構造函數ShowDog的屬性prototype。等等,我們還沒有構造函數ShowDog呢,下麵就來創建它:
function ShowDog(name, breed, weight, handler) { //這個構造函數接受各種實參,用於設置小狗的屬性 (name、breed、weight)和 表演犬的屬性(handler)。
this.name = name;
this.breed = breed;
this.weight = weight;
this.handler = handler;
}
有了這樣的構造函數後,便可將其屬性prototype設置為一個新的小狗實例了:
ShowDog.prototype = new Dog(); //我們原本可以使用前一頁創建的小狗實例,但為少使用一個變數,這裡沒有這樣做,而是直接將 一個新小狗實例賦給屬性prototype。
來看看我們到了哪一步:我們有構造函數ShowDog,可用來創建表演犬實例。我們還有一個表演犬原型,它是一個小狗實例。
下麵來將對象圖中的標簽“Dog”改為“表演犬原型”,確保它準確地反映了這些對象扮演的角色。但別忘了,表演犬原型依然是一個小狗實例。
有了構造函數ShowDog和表演犬原型後,我們需要回過頭去補充一些細節。我們將深 入研究這個構造函數,並給表演犬原型添加一些屬性和方法,讓表演犬具備所需的額外行為。
該補全原型了
我們設置了表演犬原型,但當前它只是一個空的小狗實例。現在該給它添加屬性和行為,讓它更像表演犬原型了。
要給表演犬添加的屬性和方法如下:
創建表演犬實例
至此,我們只需做最後一件事:創建一個ShowDog實例。這個實例將從表演犬原型那裡繼承表演犬特有的屬性和方法。另外,由於表演犬原型是一個小狗實例,這個表演犬也將從小狗原型那裡繼承所有的小狗行為和屬性。因此它像其他小狗一樣,也能夠發出叫聲、奔跑和搖尾。
下麵列出了前面編寫的所有代碼,還有創建表演犬實例的代碼:
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
Dog.prototype.species = "Canine";
Dog.prototype.bark = function () {
if (this.weight > 25) {
console.log(this.name + " says Woof!");
} else {
console.log(this.name + " says Yip!");
}
};
Dog.prototype.run = function () {
console.log("Run!");
};
Dog.prototype.wag = function () {
console.log("Wag!");
};
function ShowDog(name, breed, weight, handler) { //這個構造函數接受各種實參,用於設置小狗的屬性 (name、breed、weight)和 表演犬的屬性(handler)。
this.name = name;
this.breed = breed;
this.weight = weight;
this.handler = handler;
}
ShowDog.prototype = new Dog();
ShowDog.prototype.league = "Webville";
ShowDog.prototype.stack = function () {
console.log("Stack");
};
ShowDog.prototype.bait = function () {
console.log("Bait");
};
ShowDog.prototype.gait = function (kind) {
console.log(kind + "ing");
};
ShowDog.prototype.groom = function () {
console.log("Groom");
};
var scotty = new ShowDog("Scotty", "Scottish Terrier", 15, "Cookie");
scotty.bark();
scotty.stack();
console.log(scotty.league);
console.log(scotty.species);
最後的整理
我們來更深入地研究一下前面創建的各個小狗。前面對Fido進行了測試,發現它確實是小狗,下麵來看看它是否也是表演犬(我們認為它不是)。Scotty呢?前面通過測試確定了它是表演犬,但它也是小狗嗎?我們不確定。下麵順便來測試一下Fido和Scotty,看看它們都是使用哪個構造函數創建的。
var fido = new Dog("Fido", "Mixed", 38);
if (fido instanceof Dog) {
console.log("Fido is a Dog");
}
if (fido instanceof ShowDog) {
console.log("Fido is a ShowDog");
}
var scotty = new ShowDog("Scotty", "Scottish Terrier", 15, "Cookie");
if(scotty instanceof Dog) {
console.log("Scotty is a Dog");
}
if(scotty instanceof ShowDog) {
console.log("Scotty is a ShowDog");
}
console.log("Fido constructor is " + fido.constructor);
console.log("Scotty constructor is " + scotty.constructor);
通過瀏覽器控制台列印輸出結果如下圖所示:
想想為何會是這樣的結果。首先,Fido顯然只是小狗,而不是表演犬。事實 上,這完全符合我們的預期,畢竟Fido是使用構造函數Dog創建的,而這個構造函數與表演犬一點關係都沒有。
接下來,Scotty既是小狗又是表演犬。這也合情合理,不過怎麼會出現這樣的結果呢?這是因為instanceof不僅考慮當前對象的類型,還考慮它繼承的所有對象。Scotty雖然是作為表演犬創建的,但表演犬繼承了小狗,因此Scotty也是小狗。
再接下來,Fido的構造函數為Dog。這合情合理,因為它就是使用這個構造函數創建的。
最後,Scotty的構造函數也是Dog。這不合理,因為它是使用構造函數ShowDog創建的。到底是怎麼回事呢?先來看看這是如何得到的:查看屬性scotty.constructor。由於我們沒有顯式地為表演犬設置這個屬性,它將從小狗原型那裡繼承該屬性。
為何會這樣呢?坦率地說,這是一個需要修複的漏洞。正如你看到的,如果我們不顯式地設置表演犬原型的屬性constructor,就沒人會這樣做。不過,即便我們不這樣做,一切也都將正常運行,但訪問scotty.constructor時,
結果將不是預期的ShowDog,讓人感到迷惑。
不用擔心,下麵就來修複這個問題。
首先,正如你看到的,沒有正確地設置表演犬實例的屬性constructor: 它們從小狗原型那裡繼承了這個屬性。需要澄清的一點是,雖然代碼都沒問題,但給對象設置正確的構造函數是一種最佳實踐,以免有一天另一 位開發人員接手這些代碼並查看表演犬對象的情況時感到迷惑。
為修複屬性constructor不正確的問題,需要在表演犬原型中正確地設置它。這樣,創建表演犬實例時,它將繼承正確的constructor屬性,如下所示:
再次運行前面的測試,並核實表演犬實例Scotty的構造函數正確無誤。
還有一個地方可以清理:構造函數ShowDog的代碼。再來看一眼這個構造函數:
就這裡而言,既然構造函數Dog已經知道如何完成這些工作,為何不讓它去做呢?另外,雖然這個示例的代碼很簡單,但有些構造函數可能使用複雜的代碼來計算屬性的初始值。因此創建繼承另一個原型的構造函數時,都不應重覆既有的代碼。下麵來修複這個問題——先重寫代碼,再詳細介紹它們:
正如你看到的,在構造函數ShowDog中,我們調用了方法Dog.call, 以此替換了那些重覆的代碼。這裡的原理如下:call是一個內置方法,可對任何函數調用它(別忘了,Dog是一個函數)。Dog.call調用函 數Dog,將一個用作this的對象以及函數Dog的所有實參傳遞給它。下 面來詳細介紹這行代碼:
Dog.call詳解
最終代碼如下:
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
Dog.prototype.species = "Canine";
Dog.prototype.bark = function () {
if (this.weight > 25) {
console.log(this.name + " says Woof!");
} else {
console.log(this.name + " says Yip!");
}
};
Dog.prototype.run = function () {
console.log("Run!");
};
Dog.prototype.wag = function () {
console.log("Wag!");
};
function ShowDog(name, breed, weight, handler) {
Dog.call(this, name, breed, weight);
this.handler = handler;
}
ShowDog.prototype = new Dog();
ShowDog.prototype.constructor = ShowDog;
ShowDog.prototype.league = "Webville";
ShowDog.prototype.stack = function () {
console.log("Stack");
};
ShowDog.prototype.bait = function () {
console.log("Bait");
};
ShowDog.prototype.gait = function (kind) {
console.log(kind + "ing");
};
ShowDog.prototype.groom = function () {
console.log("Groom");
};
var fido = new Dog("Fido", "Mixed", 38);
var fluffy = new Dog("Fluffy", "Poodle", 30);
var spot = new Dog("Spot", "Chihuahua", 10);
var scotty = new ShowDog("Scotty", "Scottish Terrier", 15, "Cookie");
var beatrice = new ShowDog("Beatrice", "Pomeranian", 5, "Hamilton");
fido.bark();
fluffy.bark();
spot.bark();
scotty.bark();
beatrice.bark();
scotty.gait("Walk");
beatrice.groom();
Object是什麼?
小狗原型並非原型鏈的終點
前面介紹了兩個原型鏈。第一個原型鏈包含從中派生出小狗對象的小狗原型;第二個原型鏈包含從中派生出表演犬的表演犬原型,而表演犬原型又是從小狗原型派生出來的。
在這兩個原型鏈中,終點都是小狗原型嗎?實際上不是,因為小狗原型是從Object派生出來的。
事實上,你創建的每個原型鏈的終點都是Object。這是因為對於你創建的任何實例,其預設原型都是Object,除非你對其進行了修改。
可將Object視為對象始祖,所有對象都是從它派生而來的。Object實現了多個重要的方法,它們是JavaScript對
象系統的核心部分。在日常工作中,這些方法中的很多你都不會用到,但有幾個經常會用到。
你在本章前面就見到過其中一個:hasOwnProperty。每個對象都繼承了這個方法,因為歸根結底,每個對象都是從Object派生而來的。別忘了,在本章前面,我們使用了方法hasOwnProperty來確定屬性是在對象實例還是其原型中定義的。
Object定義的另一個方法是toString,但實例通常會重寫它。這個方法返回對象的字元串表示。稍後將演示如何重寫這個方法,為對象提供更準確的描述。
作為原型的Object
你可能沒有意識到,你創建的每個對象都有原型,該原型預設為Object。你可將對象的原型設置為其他對象,就像我們對錶演犬原型所做的那樣,但所有原型鏈的終點都是Object。
文章整理自:
Head First JavaScript程式設計