JavaScript原型鏈

来源:https://www.cnblogs.com/itbsl/archive/2019/02/20/10406269.html
-Advertisement-
Play Games

[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程式設計


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 博客園美化 作者:凱魯嘎吉 - 博客園 http://www.cnblogs.com/kailugaji/ 首先應該獲得JS許可權,之後才能進行如下操作。 1. 小人時間 在“設置”->“博客側邊欄公告(支持HTML代碼)(支持JS代碼)”中寫入 2. 博主頭像 博主頭像地址:凱魯嘎吉的主頁 - 博客 ...
  • 首先按照mint-ui的文檔中按需引入的要求,先執行 然後,將.babelrc文件替換了,但是後來我又將其改了(採坑過程我也改來改去),下麵附圖: 接著運行,報錯: 說是沒有安裝es2015,然後安裝cnpm install babel-preset-es2015 --save-dev 然後運行: ...
  • 什麼是AJAX? AJAX = Asynchronous JavaScript and XML(非同步的 JavaScript 和 XML)。 AJAX 不是新的編程語言,而是一種使用現有標準的新方法。 AJAX 最大的優點是在不重新載入整個頁面的情況下,可以與伺服器交換數據並更新部分網頁內容。 AJ ...
  • 一、CSS簡單規則 CSS樣式表包含3部分內容:選擇符、屬性和屬性值 其中選擇符包括基本的3種選擇器: 1、標記選擇器,如<a></a>標簽等; 2、類別選擇器,用class屬性來聲明即可; 3、id選擇器,用id來聲明,只能使用1次。 二、CSS選擇器 1、標記選擇器,例如對<a></a>標簽,下 ...
  • 當不給父元素設置寬高時,父元素的寬高會被子元素的內容撐開。但是當子元素設置浮動屬性後,子元素會溢出到父元素外,父元素的寬高也不會被撐開了,稱之為“高度塌陷”。 解決辦法: 1.給父元素設置 overflow 添加 overflow 不僅減少了代碼量,還不存在語義化的問題,但是也可能因為內容增加導致超 ...
  • js監聽瀏覽器tab視窗切換 ——IT唐伯虎 摘要:js監聽瀏覽器tab視窗切換。 if (document.hidden !== undefined) { document.addEventListener('visibilitychange', () => { console.debug(doc ...
  • 跨域報錯的原因 最開始上傳視頻成功後,video標簽的src會直接引入上傳後的服務端資源地址,然後使用canvas截圖就發生了跨域報錯的提示。 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not ...
  • transform是一些效果的集合,主要是移動、旋轉、縮放和傾斜這四種基本操作,還可以通過設置matrix矩陣來實現更複雜的效果。 變形transform可以實現2D和3D兩種效果。 變形transform本來是一個用來處理移動、旋轉、縮放和傾斜等基本操作的CSS3屬性,但該屬性除了完成其本職工作之 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...