JS原型與繼承

前言

如果你覺(jué)得JS的繼承寫(xiě)起來(lái)特別費(fèi)勁,特別艱澀,特別不倫不類,我想說(shuō),我也有同感。尤其是作為一個(gè)學(xué)過(guò)Java的人,看到JS的繼承簡(jiǎn)直要崩潰。至于為什么JS的繼承讓人如此困惑,根源當(dāng)然在于JS本身的設(shè)計(jì),即半函數(shù)式編程,半面向?qū)ο蟆?duì)歷史感興趣的人可以參考以下文章,雖然不會(huì)有恍然大悟——“原來(lái)繼承可以這么寫(xiě)”的感覺(jué),至少可以讓你對(duì)于自己的困惑找到一絲安慰。

http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html

至于我寫(xiě)這篇文章的目的,當(dāng)然是記錄一下自己的思考過(guò)程(有些東西是看了別人的代碼,在試圖理解其寫(xiě)法的目的),方便以后回頭重溫,畢竟人的忘性是很大的,另一方面則是有緣人看到這篇文章,希望它多少能幫助你在思考繼承的問(wèn)題上少走一點(diǎn)彎路。

另外免責(zé)聲明:本文寫(xiě)的是自己的思考過(guò)程,雖然力求正確,不至于誤人子弟,但是難免有疏漏和錯(cuò)誤,請(qǐng)諒解。如果能通過(guò)評(píng)論指正出來(lái),十分感謝。

正文

1.關(guān)于原型和原型鏈

說(shuō)到JS的繼承,當(dāng)然離不開(kāi)原型和原型鏈,因?yàn)樗鼈儽旧砭褪菫榱顺槿?gòu)造函數(shù)的共通部分而存在的。

1-1.困惑點(diǎn)

在原型和原型鏈的相關(guān)的問(wèn)題中,很多人比較困惑的大概是以下幾個(gè)。

<1>constructor,prototype和__proto的關(guān)系

<2>Function instanceof Object 和Object instanceof Function的結(jié)果為什么都是true

<3>為什么所有的對(duì)象的原型最終都指向Object.prototype而不是Object

(這個(gè)問(wèn)題可能不是大多數(shù)人都有的,但是我自己對(duì)理解這一點(diǎn)很是費(fèi)了一番功夫。)

1-2.需要知道的點(diǎn)

接下來(lái)說(shuō)一說(shuō)關(guān)于原型和原型鏈需要知道的一些點(diǎn)

<1>通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象的內(nèi)部原理
如下面的代碼:

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        console.log(this.name);
    }
}
var Tony = new Person('tony', 18);

其實(shí)通過(guò)new操作符創(chuàng)建對(duì)象的過(guò)程是這樣的。

function Person(name, age) {
    //1.創(chuàng)建一個(gè)對(duì)象,用this指向它。 this = {};
    //2.執(zhí)行以下方法
    this.name = name;
    this.age = age;
    this.sayName = function () {
        console.log(this.name);
    }
    //3.return this
}
var Tony = new Person('tony', 18);

<2>原型是函數(shù)的屬性prototype指向的內(nèi)容,原型本身是對(duì)象
首先必須要聲明一點(diǎn),雖然在JS里一切皆對(duì)象,所以函數(shù)也是對(duì)象。但是方便起見(jiàn),說(shuō)函數(shù)的時(shí)候仍然指普通意義上的函數(shù),即通過(guò)function關(guān)鍵字聲明的類型,而說(shuō)對(duì)象的時(shí)候指的是帶一對(duì)兒大括號(hào)的類型。
說(shuō)到這里有必要提一下,函數(shù)本質(zhì)上是一個(gè)代碼塊,是一塊普通代碼的集合,所以函數(shù)內(nèi)部是一行行的執(zhí)行語(yǔ)句,語(yǔ)句之間用分號(hào)隔開(kāi)。而對(duì)象本質(zhì)上是屬性名值對(duì)兒的集合,屬性名和屬性值之間用冒號(hào)連接在一起,不同的屬性名值對(duì)兒之間用逗號(hào)隔開(kāi)。
函數(shù)身上才有prototype屬性,用以顯式地聲明一個(gè)函數(shù)的原型。而原型本身必須是一個(gè)對(duì)象。這一點(diǎn)可以通過(guò)簡(jiǎn)單的代碼得以驗(yàn)證,不再貼圖。

<3>聲明prototype的含義
如以下代碼:

Person.prototype.sayName = function () {
    console.log(this.name);
}
function Person(name, age) {
    this.name = name;
    this.age = age;
}
var Tony = new Person('tony', 18);

這里給Person函數(shù)加了一個(gè)原型。當(dāng)通過(guò)new操作符創(chuàng)建Person對(duì)象的時(shí)候,會(huì)在構(gòu)造函數(shù)內(nèi)部創(chuàng)建空對(duì)象后,立刻在里面放了一個(gè)隱式屬性proto指向了Person的原型對(duì)象。

Person.prototype.sayName = function () {
    console.log(this.name);
}
function Person(name, age) {
    //this = {__proto__ : Person.prototype}
    this.name = name;
    this.age = age;
}
var Tony = new Person('tony', 18);

需要注意的是,這里雖然只是寫(xiě)空對(duì)象里放了一個(gè)隱式屬性proto指向Person.prototype,從本質(zhì)上來(lái)說(shuō),Person.prototype這個(gè)詞本身并不是一個(gè)變量,也不是一個(gè)對(duì)象,并沒(méi)有夾在Person對(duì)象和Person的原型中間。當(dāng)創(chuàng)建Person對(duì)象后,Tony里面有一個(gè)proto直接指向一個(gè)對(duì)象,即:

{
    sayName : function() {console.log(this.name);},
    constructor : function Person(name, age) {this.name=name;this.age=age},
    __proto__ : Object.prototype
}

使用prototype關(guān)鍵字把一個(gè)對(duì)象聲明為一個(gè)函數(shù)的原型,只是意味著通過(guò)該函數(shù)創(chuàng)建對(duì)象時(shí),該對(duì)象內(nèi)部會(huì)有一個(gè)隱式屬性proto指向這個(gè)原型。這一點(diǎn)很重要。

<4>不顯式地聲明一個(gè)構(gòu)造函數(shù)的原型地話,系統(tǒng)會(huì)構(gòu)建一個(gè)隱式的原型
如以下代碼:

function Son(name, age, hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
    this.getFullName = function () {
        console.log(this.lastName + this.name);
    }
}

var Datou = new Son('Datou', 18, 'scapegoat');

系統(tǒng)會(huì)隱式地為Son構(gòu)造函數(shù)創(chuàng)建一個(gè)原型對(duì)象

{
    constructor : function Son() {...},
    __proto__ : Object.prototype
}

這里還是想再?gòu)?qiáng)調(diào)一下,雖然我寫(xiě)了Object.prototype,千萬(wàn)不要把它當(dāng)成一個(gè)存在的變量或?qū)ο螅踔炼妓悴簧弦粋€(gè)“指向”,而只是一個(gè)“指代”,指代的是Object的原型對(duì)象本身,那個(gè)帶大括號(hào)的對(duì)象。只不過(guò)由于它內(nèi)部的代碼很多,也沒(méi)有一個(gè)名字,所以我用prototype指代了一下。總而言之,我不想讓你誤以為存在一個(gè)叫Son.prototype的中間變量和對(duì)象,從而形成以下錯(cuò)誤印象:
Datou.proto → Son.prototype → {(隱式創(chuàng)建的)原型對(duì)象} → Object.prototype → {一大堆系統(tǒng)提供方法的集合}
而真實(shí)的情況是:
Datou.proto → {(隱式創(chuàng)建的)原型對(duì)象} → {一大堆系統(tǒng)提供方法的集合}

<5>實(shí)例對(duì)象的constructor是從原型對(duì)象復(fù)制過(guò)來(lái)的
個(gè)人感覺(jué),和prototype以及proto相比,constructor在繼承中的作用不是很大。
再來(lái)一個(gè)聲明,在本文討論的范圍內(nèi),對(duì)象分為三種,即實(shí)例對(duì)象,原型對(duì)象和Object.prototype。實(shí)例對(duì)象指的是通過(guò)new操作符創(chuàng)建出來(lái)的對(duì)象,原型對(duì)象指的是通過(guò)prototype關(guān)鍵字聲明的對(duì)象,而Object.prototype,指的是哪個(gè)一對(duì)兒大括號(hào),里面有一堆系統(tǒng)自定義的方法的對(duì)象。
還以下面的代碼舉例:

function Son(name, age, hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
    this.getFullName = function () {
        console.log(this.lastName + this.name);
    }
}

var Datou = new Son('Datou', 18, 'scapegoat');

Datou這個(gè)對(duì)象里有一個(gè)constructor指向系統(tǒng)隱式創(chuàng)建的Son的原型這一點(diǎn)我們?cè)偈煜げ贿^(guò)了,以至于我們可能會(huì)忽略其實(shí)Datou內(nèi)部并沒(méi)有一個(gè)叫constructor的屬性,它只是通過(guò)隱式屬性proto調(diào)用的原型上的constructor。這一點(diǎn)通過(guò)在控制臺(tái)打印Datou.hasOwnProperty('constructor')返回結(jié)果為false得以驗(yàn)證。
結(jié)論就是只有原型對(duì)象上才有constructor屬性,把一個(gè)對(duì)象當(dāng)做是誰(shuí)的原型,這個(gè)原型對(duì)象的constructor就指向誰(shuí)。

<6>原型對(duì)象為什么最終都鏈接到Object.prototype上
以下面這個(gè)簡(jiǎn)化版的代碼舉例:

Son.prototype.sayName = function () {
    console.log(this.name);
}

function Son(name) {
    this.name = name;
}

var Datou = new Son('Datou');

很顯然,系統(tǒng)會(huì)首先創(chuàng)建一個(gè)Son的原型對(duì)象,即:

{
    sayName : function() {console.log(this.name)},
    constructor : function Son(name) {this.name=name},
    __proto__ : Object.prototype
}

里面有我們自定義的函數(shù)屬性sayName,同時(shí)會(huì)有一個(gè)constructor指向Son函數(shù),最后還有一個(gè)proto指向Object的原型。
在這里你會(huì)很自然的想到兩點(diǎn)。第一點(diǎn)是這個(gè)原型對(duì)象雖然看上去是我們手動(dòng)通過(guò)字面量形式寫(xiě)出來(lái)的,但其實(shí)一定是通過(guò)new出來(lái)的所以它內(nèi)部才有一個(gè)隱式屬性__proto。第二點(diǎn)是既然proto指向Object的原型,那Son的原型對(duì)象一定是通過(guò)Object函數(shù)new出來(lái)的。
事實(shí)確實(shí)如此。通過(guò)字面量的形式創(chuàng)建對(duì)象跟通過(guò)new Object()的方式創(chuàng)建對(duì)象本質(zhì)上是一樣的。所以通過(guò)以下代碼創(chuàng)建Datou的過(guò)程,以一種比較全面的角度來(lái)解讀是下面這樣的:

  1. 執(zhí)行var obj = new Object();
    1-1)this = {};
    1-2)this.proto = {一大堆系統(tǒng)提供方法的集合,由于當(dāng)前對(duì)象在Object函數(shù)中創(chuàng)建,所以該隱式屬性指向Object的原型對(duì)象};
    1-3)this.sayName = function() {console.log(this.name)};
    1-4)由于聲明了prototype所以this.constuctor = function Son(name) {this.name=name};
    1-5)return this
  2. 執(zhí)行var Datou = new Son('Datou');
    2-1)this = {};
    2-2)this.proto={由于當(dāng)前對(duì)象在Son函數(shù)中創(chuàng)建,所以該隱式屬性指向Son的原型對(duì)象,即obj指代的對(duì)象}
    2-3)this.name = 'Datou';
    2-4)return this;
    最終,Datou就代表了如下一個(gè)對(duì)象
{
    name : "Datou",
    __proto__ : {包含sayName方法的那個(gè)原型對(duì)象}
}

看到這兒,應(yīng)該就能明白為什么所有的對(duì)象最終都會(huì)連接到Object的原型上了。
而這種通過(guò)隱式屬性proto不斷往上找原型的鏈條就是我們通常意義上所說(shuō)的原型鏈。

2.JS繼承的實(shí)現(xiàn)方式

如果看這篇文章之前你已經(jīng)查詢過(guò)百度很多遍,想必一定看到過(guò)繼承實(shí)現(xiàn)方式的演變歷史。下面談?wù)勛约簩?duì)繼承的理解,為此我準(zhǔn)備了一個(gè)例子,按照這個(gè)例子把代碼寫(xiě)出來(lái),基本上就能學(xué)會(huì)JS的繼承了。
頂部有一個(gè)Animal函數(shù),它里面有name,food和eat三個(gè)屬性,其中eat屬性是一個(gè)方法。Cat函數(shù)和Dog函數(shù)分別繼承自Animal,而Cat函數(shù)還有一個(gè)自己的屬性catMouse,最后通過(guò)Cat函數(shù)創(chuàng)建加菲貓,通過(guò)Dog函數(shù)創(chuàng)建歐弟。通過(guò)加菲貓調(diào)用eat方法,執(zhí)行的結(jié)果是加菲貓正在吃千層面,通過(guò)加菲貓調(diào)用catchMouse方法的話則打印我抓到了一只老鼠。通過(guò)歐弟調(diào)用eat方法,執(zhí)行的結(jié)果是歐弟正在啃骨頭。


pic.png
2-1.首先想到的寫(xiě)法

通過(guò)百度或者加入一點(diǎn)自己的思考,最開(kāi)始得出的寫(xiě)法可能是這樣的。

function Animal(name, food) {
    this.name = name;
    this.food = food;
    this.eat = function () {
        console.log(this.name + " is eatting " + this.food);
    }
}

function Cat(name, food) {
    Animal.call(this, name, food);
    this.catchMouse = function () {
        console.log("Hey, John! I got a big mouse!")
    }
}

function Dog(name, food) {
    Animal.call(this, name, food);
}

var Garfield = new Cat("Garfield", 'lasagne');
var Odie = new Dog("Odie", "bone");

Garfield.eat();
Garfield.catchMouse();
Odie.eat();

這種寫(xiě)法從功能實(shí)現(xiàn)的角度來(lái)說(shuō)已經(jīng)沒(méi)有問(wèn)題了,但是沒(méi)有用到原型很讓人不爽。怎么說(shuō)呢,之所以出現(xiàn)原型,就是為了提取共通的部分,這也是繼承的題中之義。上面這種寫(xiě)法,單純是使用Animal函數(shù)的call方法為自己初始化變量,其實(shí)本質(zhì)上是“借腹生子”。而且看起來(lái)是寫(xiě)的代碼少了,但實(shí)際上執(zhí)行的步驟可是一步都不少。而且,函數(shù)類的屬性一般都要寫(xiě)到原型里,不然要原型干嘛。像現(xiàn)在這種寫(xiě)法的話,相當(dāng)于每個(gè)Cat對(duì)象里都會(huì)存放一份eat函數(shù)和catchMouse函數(shù),而每個(gè)Dod對(duì)象里都會(huì)放一份eat函數(shù),這根本沒(méi)有顯示出繼承的特點(diǎn)。

2-2.加上原型后的效果

接下來(lái)把共通的部分抽取出來(lái)放到原型里,并用原型鏈鏈接起來(lái)。

Animal.prototype.eat = function () {
    console.log(this.name + " is eatting " + this.food);
}
function Animal(name, food) {
    this.name = name;
    this.food = food;
}

Cat.prototype.catchMouse = function () {
    console.log("Hey, John! I got a big mouse!")
}
Cat.prototype = new Animal();
function Cat(name, food) {
    Animal.call(this, name, food);
}

Dog.prototype = new Animal();
function Dog(name, food) {
    Animal.call(this, name, food);
}

var Garfield = new Cat("Garfield", 'lasagne');
var Odie = new Dog("Odie", "bone");

Garfield.eat();
Garfield.catchMouse();
Odie.eat();

這個(gè)時(shí)候就開(kāi)始遇到一個(gè)很嚴(yán)重的問(wèn)題。
假如沒(méi)有加菲貓,只有歐弟,即子函數(shù)的原型上沒(méi)有自己獨(dú)有的方法。

Animal.prototype.eat = function () {
    console.log(this.name + " is eatting " + this.food);
}
function Animal(name, food) {
    this.name = name;
    this.food = food;
}

Dog.prototype = new Animal();
function Dog(name, food) {
    Animal.call(this, name, food);
}

var Odie = new Dog("Odie", "bone");
Odie.eat();

這就沒(méi)問(wèn)題,因?yàn)镈og的原型是Animal對(duì)象,即Odie里面有一個(gè)隱式屬性proto指向一個(gè)Animal對(duì)象(它沒(méi)有名字),而Animal的原型是一個(gè)包含eat函數(shù)的對(duì)象,所以Animal對(duì)象里面有一個(gè)隱式屬性proto指向那個(gè)包含eat函數(shù)的對(duì)象。這樣的話,Odie調(diào)用eat方法的話,自己沒(méi)有,通過(guò)自己的proto找到那個(gè)Animal對(duì)象,結(jié)果它也沒(méi)有,再通過(guò)Animal對(duì)象的proto找到那個(gè)包含eat函數(shù)的對(duì)象,執(zhí)行其中的eat方法。
然而換種想法,其實(shí)這種情況下還是有一點(diǎn)值得思考的,就是為什么不直接寫(xiě)Dog.prototype = Animal.prototype呢。可以直接這么寫(xiě),而且直接寫(xiě)其實(shí)比中間通過(guò)一個(gè)Animal對(duì)象要好。這么寫(xiě)的話,Odie的proto直接指向包含eat方法的對(duì)象,正好就是自己想要的效果。剛才那種寫(xiě)法反而每次都創(chuàng)建出一個(gè)多余的Animal對(duì)象,里面有兩個(gè)屬于自己的屬性(沒(méi)有通過(guò)call改變this指向,通過(guò)new創(chuàng)建的時(shí)候可以寫(xiě)參數(shù)也可以不寫(xiě)參數(shù),一般不寫(xiě),沒(méi)必要),這兩個(gè)參數(shù)的值最終都是undefined。實(shí)際上這個(gè)對(duì)象唯一的作用就是里面有一個(gè)proto指向自己的構(gòu)造函數(shù)的原型。
Dog.prototype = Animal.prototype其實(shí)有一個(gè)專業(yè)的叫法——共享原型。但是它不是總能奏效,比如加菲貓這頭,它還需要有一個(gè)專屬于貓科動(dòng)物的特性——抓老鼠。
Cat既要有自己的一個(gè)原型(是一個(gè)對(duì)象),里面包含一個(gè)catchMouse方法,以便Garfield的proto指向這個(gè)原型,同時(shí)又要讓加菲貓的原型直接或間接地指向Animal的原型。以共享原型的方式直接指向的話是沒(méi)戲的,因?yàn)镃at.prototype=Animal.prototype的話就沒(méi)有中間的對(duì)象了,而我恰好需要一個(gè)中間的對(duì)象在這兒。以開(kāi)始的那種先執(zhí)行new Animal()對(duì)象再間接指向最終目標(biāo)的話,會(huì)出現(xiàn)以下3中結(jié)果。
1)先寫(xiě)Cat.prototype = new Animal();再寫(xiě)Cat.prototype={catchMouse:function() {...}}的話,前者會(huì)被后者覆蓋掉,導(dǎo)致最終沒(méi)法指向Animal的eat方法。
2)先寫(xiě)Cat.prototype = new Animal();再寫(xiě)Cat.prototype.catchMouse=function() {...}的話,每個(gè)創(chuàng)建出來(lái)的Cat對(duì)象里仍然會(huì)有一份無(wú)用的Cat對(duì)象的屬性值這一點(diǎn)并沒(méi)有變。而且明明是Cat的方法,卻寫(xiě)到了Animal對(duì)象里,語(yǔ)義上好像不好。
3)先寫(xiě)Cat.prototype.catchMouse=function() {...}再寫(xiě)Cat.prototype = new Animal();的話,前者會(huì)被后者覆蓋,導(dǎo)致實(shí)際上并沒(méi)有添加屬于自己的特有方法。
這個(gè)時(shí)候靜下心來(lái)想一想,自己到底想要達(dá)到什么效果?其實(shí)就是Cat函數(shù)的原型對(duì)象確實(shí)存在,它里面有一個(gè)Cat函數(shù)才有的catchMouse方法,這個(gè)原型對(duì)象里應(yīng)該有一個(gè)proto直接指向Animal的原型。但是就是辦不到。于是我手動(dòng)地在創(chuàng)建Cat的原型對(duì)象后,給他顯式地添加一個(gè)proto屬性,讓它指向Animal的原型,效果如下:

Animal.prototype.eat = function () {
    console.log(this.name + " is eatting " + this.food);
}
function Animal(name, food) {
    this.name = name;
    this.food = food;
}

Cat.prototype.catchMouse = function () {
    console.log("Hey, John! I got a big mouse!")
}
Cat.prototype.__proto__ = Animal.prototype;
function Cat(name, food) {
    Animal.call(this, name, food);
}

var Garfield = new Cat("Garfield", 'lasagne');
Garfield.eat();
Garfield.catchMouse();

試了一下,結(jié)果居然是可以的。然而,IE瀏覽器下并不好使,其它瀏覽器倒是好使。
其實(shí)這個(gè)時(shí)候,退而求其次,既然看起來(lái)沒(méi)法同時(shí)做到這兩點(diǎn),把Cat函數(shù)的catchMouse屬性不用原型實(shí)現(xiàn),而是老老實(shí)實(shí)地寫(xiě)到函數(shù)內(nèi)部也還好。代碼重復(fù)就重復(fù)好了,不是徹底的繼承就不徹底好了,至少?gòu)墓δ苌蟻?lái)講也實(shí)現(xiàn)了要求。

Animal.prototype.eat = function () {
    console.log(this.name + " is eatting " + this.food);
}
function Animal(name, food) {
    this.name = name;
    this.food = food;
}

Cat.prototype = Animal.prototype;
function Cat(name, food) {
    Animal.call(this, name, food);
    this.catchMouse = function () {
        console.log("Hey, John! I got a big mouse!")
    }
}

var Garfield = new Cat("Garfield", 'lasagne');
Garfield.eat();
Garfield.catchMouse();

退而求其次也行,但是這看起來(lái)是不可調(diào)和的矛盾,真的沒(méi)有辦法了么?答案是有的。

2-3.最后改進(jìn)后的結(jié)果

重新捋一下需求和思路。寫(xiě)到這種地步,Animal的非函數(shù)屬性(name和food)采用了一種非本質(zhì)上繼承,但是也還好的方式實(shí)現(xiàn)了,現(xiàn)在主要是想鏈接到Animal的原型上,獲取到eat方法。Cat函數(shù)要有自己的方法,所以屬于自己的原型對(duì)象必須存在。即Cat.prototype.catchMouse=function(){...}是一定有的,這個(gè)對(duì)象必須實(shí)實(shí)在在地存在。然后這個(gè)對(duì)象需要有一個(gè)proto鏈接到Animal的原型,強(qiáng)制加proto是不現(xiàn)實(shí)的。其實(shí)仔細(xì)想行,為什么讓這個(gè)原型對(duì)象里有一個(gè)proto直接指向Animal的原型那么難(其實(shí)是不可能的),這又回到開(kāi)頭的問(wèn)題上,顯式聲明prototype和proto是怎么回事兒,以及什么關(guān)系上。
使用prototype關(guān)鍵字把一個(gè)對(duì)象聲明為一個(gè)函數(shù)的原型,只是意味著通過(guò)該函數(shù)創(chuàng)建對(duì)象時(shí),該對(duì)象內(nèi)部會(huì)有一個(gè)隱式屬性proto指向這個(gè)原型。
所以Animal的原型只會(huì)在new Animal()的對(duì)象里才會(huì)被proto引用。而Cat的原型,本身已經(jīng)是一個(gè)對(duì)象(里面有一個(gè)catchMouse函數(shù)屬性)了,它就不可能是Animal的對(duì)象。
既然我沒(méi)法直接指向你,而我所需要的又只是你(Animal的原型),而不是Animal對(duì)象(指向Animal對(duì)象會(huì)有多余的屬性)。既然無(wú)論如何都只能是間接地指向你,現(xiàn)有的那個(gè)我又不喜歡,那我干脆創(chuàng)建一個(gè)新的空對(duì)象作為間接內(nèi)容指向你好了。于是可以創(chuàng)建一個(gè)新的函數(shù)(內(nèi)容為空),讓這個(gè)函數(shù)的原型也是你,這個(gè)函數(shù)構(gòu)造出的對(duì)象里其它什么都沒(méi)有,只有一個(gè)proto指向你,然后我自己加我特有的內(nèi)容時(shí)加到這個(gè)新的對(duì)象上了。雖然仍然有語(yǔ)義上不好的感覺(jué),至少?zèng)]有多余的Animal對(duì)象的屬性了。

Animal.prototype.eat = function () {
    console.log(this.name + " is eatting " + this.food);
}
function Animal(name, food) {
    this.name = name;
    this.food = food;
}

function F() {}
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.catchMouse = function () {
    console.log("Hey, John! I got a big mouse!")
}

function Cat(name, food) {
    Animal.call(this, name, food);
}

var Garfield = new Cat("Garfield", 'lasagne');
Garfield.eat();
Garfield.catchMouse();

其實(shí)這一塊代碼還可以設(shè)計(jì)成一個(gè)單獨(dú)的函數(shù):

function extend(son, father) {
    function F() {};
    F.prototype = father.prototype;
    son.prototype = new F();
}

Animal.prototype.eat = function () {
    console.log(this.name + " is eatting " + this.food);
}
function Animal(name, food) {
    this.name = name;
    this.food = food;
}

extend(Cat, Animal);
Cat.prototype.catchMouse = function () {
    console.log("Hey, John! I got a big mouse!")
}

function Cat(name, food) {
    Animal.call(this, name, food);
}

var Garfield = new Cat("Garfield", 'lasagne');
Garfield.eat();
Garfield.catchMouse();

這樣的寫(xiě)法已經(jīng)很好地完成了任務(wù)了。再錦上添花一下的話,需要思考一下constructor的事兒。從這里可以看出來(lái),constructor對(duì)應(yīng)JS的原型以及原型鏈來(lái)講作用遠(yuǎn)不及prototype和proto重要。但是為了更符合JS原型和原型鏈的體系,有必要再加點(diǎn)東西。
上面的代碼中,通過(guò)Garfield訪問(wèn)constructor屬性會(huì)打印出Animal函數(shù)。原因是Cat的prototype是新建的空對(duì)象,而那個(gè)空對(duì)象自己也沒(méi)有constructor,它的proto才有,它的proto是Animal的prototype,結(jié)果可想而知。所以為了,體現(xiàn)出Garfield是在Cat函數(shù)中創(chuàng)建出來(lái)地這一點(diǎn),有必要加上以下代碼:
son.prototype.constructor = son;
結(jié)果代碼如下:

function extend(son, father) {
    function F() {};
    F.prototype = father.prototype;
    son.prototype = new F();
    son.prototype.constructor = son;
}

Animal.prototype.eat = function () {
    console.log(this.name + " is eatting " + this.food);
}
function Animal(name, food) {
    this.name = name;
    this.food = food;
}

extend(Cat, Animal);
Cat.prototype.catchMouse = function () {
    console.log("Hey, John! I got a big mouse!")
}

function Cat(name, food) {
    Animal.call(this, name, food);
}

var Garfield = new Cat("Garfield", 'lasagne');
Garfield.eat();
Garfield.catchMouse();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,730評(píng)論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,118評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,873評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,266評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,482評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,846評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,025評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,279評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,684評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,953評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,751評(píng)論 3 394
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,016評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容