JavaScript之原型鏈的解讀

JavaScript中惰说,原型鏈作為一個(gè)基礎(chǔ)志衣,老生長(zhǎng)談汹押,今天我們就來(lái)深入的解讀一下原型鏈矿筝。

本章主要講的是下面幾點(diǎn),可以根據(jù)需要進(jìn)行閱讀:

  • 函數(shù)與對(duì)象
  • 對(duì)于prototype的認(rèn)識(shí)
  • 對(duì)于<code>__proto__</code>的的認(rèn)識(shí)
  • prototype和<code>__proto__</code>的關(guān)系
  • instanceof操作符到底是怎么穿梭的
  • [[prototype]]鏈屬性的訪問(wèn)
  • [[prototype]]鏈上的屬性設(shè)置與屬性屏蔽
  • 關(guān)于prototype中的constructor屬性
  • 當(dāng)我們?cè)谑褂?code>new的時(shí)候到底發(fā)生了什么
  • 應(yīng)用:兩種繼承的設(shè)計(jì)模式
  • 函數(shù)與對(duì)象到底是什么關(guān)系

1. 函數(shù)與對(duì)象

我們都知道棚贾,JavaScript中窖维,一切都是對(duì)象,函數(shù)也是對(duì)象妙痹,數(shù)組也是對(duì)象铸史,但是數(shù)組是對(duì)象的子集,而對(duì)于函數(shù)來(lái)說(shuō)怯伊,函數(shù)與對(duì)象之間有一種“雞生蛋蛋生雞”的關(guān)系琳轿,我們會(huì)在最后進(jìn)行總結(jié)。

  1. 所有的對(duì)象<b>都是</b>由Object繼承而來(lái)耿芹,而Object對(duì)象卻是一個(gè)函數(shù)崭篡。
  1. 對(duì)象<b>都是</b>由函數(shù)來(lái)創(chuàng)建的。

對(duì)于上述的第一點(diǎn)猩系,前半部分會(huì)在后面的解釋中講到媚送,而對(duì)于后半部分中燥,在控制臺(tái)中輸入typeof Object寇甸,顯然輸出的是function

上述的第二點(diǎn),我們可以看一下下面的例子拿霉。

var obj = { a: 1, b: 2}
var arr = [2, 'foo', false]

表面上來(lái)看吟秩,好像不存在函數(shù)創(chuàng)建對(duì)象,而實(shí)際上绽淘,以上的過(guò)程是這樣子的:

var obj = new Object()
obj.a = 1
obj.b = 2

var arr = new Array()
arr[0] = 2
arr[1] = 'foo'
arr[2] = false
//typeof Object === 'function'  
//typeof Array === 'function'

2. 對(duì)于prototype的認(rèn)識(shí)

每一個(gè)<b>函數(shù)</b>都有一個(gè)屬性叫做prototype涵防,它的屬性值是一個(gè)對(duì)象,在這個(gè)對(duì)象中默認(rèn)有一個(gè)constructor屬性沪铭,指向這個(gè)函數(shù)的本身壮池。如下圖:

3. 對(duì)于<code>__proto__</code>的的認(rèn)識(shí)

<code>__proto__</code>是隱式原型,通常也寫(xiě)作[[prototype]]每一個(gè)<b>對(duì)象</b>都有一個(gè)這樣的隱藏屬性杀怠,<b>它引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype椰憋。</b>(注:并不是所有瀏覽器都實(shí)現(xiàn)了對(duì)于對(duì)象的隱式原型的提供!)

需要注意的是赔退,函數(shù)也是對(duì)象橙依,自然它也有__proto__

可見(jiàn)硕旗,<code>__proto__</code>和prototype并不相同(有例外窗骑,存在指向相同的情況),那兩者有什么樣的聯(lián)系呢漆枚,繼續(xù)往下看创译。

4. prototype和<code>__proto__</code>的關(guān)系

前面我們講到了兩個(gè)很重要的點(diǎn):

  1. 每一個(gè)<b>函數(shù)</b>都有一個(gè)屬性叫做prototype,它的屬性值是一個(gè)對(duì)象浪读。
  1. 每一個(gè)<b>對(duì)象</b>都有一個(gè)隱式原型<code>__proto__</code>昔榴,它引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype

所以碘橘,下面讓我們來(lái)看一段代碼看看兩者之間的關(guān)系:

var o1 = new Object()
var o2 = new Object()

上面的Object作為構(gòu)造函數(shù)創(chuàng)建了兩個(gè)對(duì)象o1o2互订。

看一下圖解:

結(jié)合上面的兩句話:

  1. function Object在這里作為一個(gè)構(gòu)造函數(shù),毫無(wú)疑問(wèn)它是一個(gè)函數(shù)痘拆,那么自然有一個(gè)prototype屬性仰禽。
  2. <code>__proto__</code>引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype。于是纺蛆,o1o2對(duì)象都是由function Object 創(chuàng)建出來(lái)的吐葵,那么自然的,它就指向(引用)了創(chuàng)建它們的函數(shù)(Object)的prototype屬性桥氏。

那我們?cè)賮?lái)看如果是一個(gè)普通的構(gòu)造函數(shù)而不是內(nèi)置的呢温峭?一樣的道理,這里我們就不再贅述字支。

function foo() {}
var f1 = new foo()
var f2 = new foo()

<b>注意:這里有一個(gè)特例凤藏!</b>
對(duì)于Object.prototype來(lái)說(shuō)奸忽,它的__proto__null,這是一個(gè)<b>特例揖庄。</b>

同時(shí)栗菜,我們要注意圖里面有一個(gè)Foo.prototype,它的__proto__指向了Object.prototype蹄梢。這個(gè)是因?yàn)椋?lt;b>一切的對(duì)象都是由Object繼承而來(lái)</b>疙筹,也就是說(shuō)Foo.prototype這個(gè)對(duì)象也是由Object構(gòu)造的,所以說(shuō)Foo.prototype.__proto__指向(引用)了Object.prototype禁炒,這個(gè)也符合我們上面所述的<b>每一個(gè)<b>對(duì)象</b>都有一個(gè)隱式原型<code>__proto__</code>而咆,它引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype</b>。

到這里幕袱,似乎prototype__proto__關(guān)系已經(jīng)很明朗的翘盖,但是你有沒(méi)有發(fā)現(xiàn)還有一個(gè)坑,我們從頭到尾都在圍繞function Object()這個(gè)東西凹蜂,那我們會(huì)不會(huì)考慮??這個(gè)鬼東西是從哪里來(lái)的呢馍驯?

難道憑空出現(xiàn)?顯然玛痊,不存在的汰瘫!畢竟,存在即合理擂煞。

那函數(shù)是怎么創(chuàng)建出來(lái)的呢混弥?我們繼續(xù)來(lái)看一段代碼,這段代碼可能你很少見(jiàn)对省,但是如果你讀過(guò)紅寶書(shū)函數(shù)的一章蝗拿,你一定不會(huì)感到陌生!

function foo(a, b) {
  return a + b
}
console.log(foo(1, 2)) //3

var boo = new Function('a', 'b', 'return a + b') //Function大寫(xiě)
console.log(boo(1,2)) //3

以上蒿涎,第二種寫(xiě)法出現(xiàn)了大寫(xiě)的Function哀托。(不推薦這么寫(xiě)。因?yàn)檫@是一種創(chuàng)建動(dòng)態(tài)函數(shù)的寫(xiě)法劳秋,原因參考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function

從上面的代碼可知仓手,函數(shù)是被Function創(chuàng)建的。

所以玻淑,function Object是由Function創(chuàng)建的嗽冒,那么Object.__proto === Function.prototype也就不言而喻了,于是就有下面的一張圖补履。

在這張圖中添坊,FooObject這兩個(gè)函數(shù)的__proto__就是指向Function.prototype了。

這里又有一個(gè)<b>特例</b>s锎浮(相信我贬蛙,這是最后一個(gè)特例了??~)

沒(méi)錯(cuò)驰弄,你會(huì)發(fā)現(xiàn)一個(gè)坑?為什么Function.__proto__指向了Function.prototype??速客?這又是什么操作?

我們來(lái)理一下思路:函數(shù)是由Function創(chuàng)建的五鲫,那么Function也是一個(gè)函數(shù)溺职,那么它有沒(méi)有可能是自己搞自己的呢???

答案是肯定的位喂。

于是浪耘,函數(shù)是由Function創(chuàng)建的,那么Function由自身創(chuàng)建塑崖,所以Function.__proto__就指向了創(chuàng)建它的函數(shù)(也就是自己)的prototype七冲。

那最后,把Foo.prototype规婆、Object.prototype澜躺、Function.prototype__proto__連起來(lái),就可以得到下面這一張圖抒蚜。(紅色標(biāo)識(shí)即為特例)

最后掘鄙,再次總結(jié)一下:

  • 所有的對(duì)象<b>都是</b>由Object繼承而來(lái),對(duì)象<b>都是</b>由函數(shù)來(lái)創(chuàng)建的嗡髓。
  • 每一個(gè)<b>函數(shù)</b>都有一個(gè)屬性叫做prototype操漠,它的屬性值是一個(gè)對(duì)象。
  • 每一個(gè)<b>對(duì)象</b>都有一個(gè)隱式原型<code>__proto__</code>饿这,它引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype浊伙。

5. instanceof操作符到底是怎么穿梭的

既然講到了__proto__prototype,那么密不可分的就是instanceof操作符了长捧。

對(duì)于 A instanceof B來(lái)說(shuō)嚣鄙,它的判斷規(guī)則是:沿著A__proto__這條線來(lái)找,同時(shí)沿著B(niǎo)的prototype這條線來(lái)找串结,如果兩條線能找到同一個(gè)引用拗慨,即同一個(gè)對(duì)象,那么就返回true奉芦。如果找到終點(diǎn)還未重合赵抢,則返回false

所以声功,不熟悉的??就可以通過(guò)上面的那個(gè)總圖來(lái)進(jìn)行判斷到底是返回true還是false烦却。

那么,我們來(lái)舉個(gè)??:

function fn {}
var f1 = new fn();
console.log(f1 instanceof Object);//true
console.log(f1 instanceof fn);//true

顯然先巴,沿著鏈條穿梭成立其爵!

再來(lái)看幾個(gè)喜聞樂(lè)見(jiàn)的:

console.log(Object instanceof Function);//true
console.log(Function instanceof Object);//true
console.log(Function instanceof Funciton);//true

所以冒冬,instanceof操作符機(jī)制就不言而喻了。

6. [[prototype]]鏈屬性的訪問(wèn)

眾所周知摩渺,JavaScript中的繼承是通過(guò)[[prototype]]鏈來(lái)實(shí)現(xiàn)的(也叫原型鏈)简烤。

看下面代碼:

function foo (){}
var f1 = new foo()
f1.a = 10

foo.prototype.a=1
foo.prototype.b=2

console.log(f1.a) //10
console.log(f1.b) //2
console.log(f1.c) // undefined

訪問(wèn)一個(gè)對(duì)象的屬性時(shí),先在這個(gè)對(duì)象自身屬性中查找摇幻,如果沒(méi)有横侦,再沿著__proto__這條鏈向上找,這就是[[prototype]]鏈(原型鏈)绰姻,如果一直找不到枉侧,那么最后會(huì)返回undefined

那如何區(qū)分這個(gè)屬性是實(shí)例對(duì)象中的(比如說(shuō)上面new出來(lái)的對(duì)象f1)還是通過(guò)[[prototype]]鏈找到的呢狂芋?

答案就是hasOwnProperty榨馁,同時(shí),在for...in循環(huán)中帜矾,要注意該遍歷會(huì)遍歷出包括原型的所有屬性翼虫。

我們可以對(duì)上面代碼的ab進(jìn)行檢測(cè):

function foo (){}
var f1 = new foo()
f1.a = 10

foo.prototype.a=1
foo.prototype.b=2

console.log(f1.a)//10
console.log(f1.b)//2

console.log(f1.hasOwnProperty('a')) //true
console.log(f1.hasOwnProperty('b')) //false

在這里,本身f1是沒(méi)有hasOwnProperty方法的屡萤,并且蛙讥,foo.prototype也是沒(méi)有的。那其實(shí)它是從Object.prototype中繼承而來(lái)的灭衷〈温可見(jiàn),<b>[[prototype]]鏈最終的位置就是Object.prototype</b>翔曲。以下是Object.prototype的一些屬性和方法迫像。

7. [[prototype]]鏈上的屬性設(shè)置與屬性屏蔽

先來(lái)看一下這段代碼:

var parentObject = {
  a: 1,
  b: 2
};
var childObject = {};
console.log(childObject); // > Object {}

childObject.__proto__ = parentObject;
console.log(childObject); // > Object {}
childObject.c = 3;
childObject.a = 2;
console.log(parentObject); // Object {a: 1, b: 2}
console.log(childObject); // > Object {c: 3, a: 2}

這是一個(gè)很簡(jiǎn)單的屬性設(shè)置,但是其實(shí)里面存在著[[prototype]]鏈屬性設(shè)置的機(jī)制??瞳遍。

如下:

  • 如果屬性c不是直接存于childObject上闻妓,[[Prototype]]鏈就會(huì)被遍歷,如果[[Prototype]]鏈上找不到c掠械,c這時(shí)就會(huì)被直接添加到childObject上由缆。
  • 如果這時(shí)屬性a存在于原型鏈上層而不存在于childObject中,賦值語(yǔ)句childObject.a = 2卻不會(huì)修改到parentObject中的a猾蒂,而是直接把a作為一個(gè)新屬性添加到了childObject上均唉。

于此同時(shí),也就發(fā)生了屬性屏蔽??肚菠。

此時(shí)會(huì)發(fā)現(xiàn)舔箭,賦值完了以后,parentObjecta屬性沒(méi)有被修改,而childObject中新增了一個(gè)a屬性层扶,所以現(xiàn)在就會(huì)出現(xiàn)一個(gè)問(wèn)題箫章,parentObjecta屬性再也不能通過(guò)childObject.a的方式被訪問(wèn)到了。

在這里镜会,就發(fā)生了屬性屏蔽檬寂,childObject中包含的a屬性會(huì)屏蔽原型鏈上層所有的a屬性,因?yàn)?code>childObject.a總會(huì)選擇原型鏈中最底層的a屬性戳表。

但實(shí)際上桶至,屏蔽比我們想象中的更復(fù)雜。下面我們一起來(lái)分析一下a不直接存在于childObject中扒袖,而是存在于原型鏈上層時(shí), 執(zhí)行childObject.a = 2語(yǔ)句會(huì)出現(xiàn)的三種情況亩码。

  1. 如果在[[Prototype]]鏈上層存在名為a的普通數(shù)據(jù)訪問(wèn)屬性季率,并且沒(méi)有被標(biāo)記為只讀(writable: false),那就會(huì)直接在childObject中添加一個(gè)名為a的新屬性描沟,它是屏蔽屬性飒泻,這個(gè)情況就是上文例子中發(fā)生的情況。

  2. 如果在[[Prototype]]鏈上層存在a吏廉,但它被標(biāo)記為只讀(writable: true)泞遗,那么無(wú)法修改已有屬性或者在childObject上創(chuàng)建屏蔽屬性,嚴(yán)格模式下執(zhí)行這個(gè)操作還會(huì)拋出錯(cuò)誤席覆。

var parentObject = {};  
Object.defineProperty(parentObject, "a", {
    value: 2,
    writable: false, // 標(biāo)記為不可寫(xiě)
    enumerable: true //可遍歷
});
var childObject = {
    b: 3
};
childObject.__proto__ = parentObject; // 綁定原型
childObject.a = 10;
console.log(childObject.a);  // 2
console.log(childObject);  // > Object {b: 3}
console.log(parentObject); // Object {a: 2}
  1. 如果在[[Prototype]]鏈上層存在a并且它被定義成了一個(gè)setter函數(shù)史辙,那就一定會(huì)調(diào)用這個(gè)setter函數(shù)。a不會(huì)被添加到childObject佩伤,上層的setter也不會(huì)被重新定義聊倔。
var parentObject = {
    set a(val) { //這是set函數(shù),相當(dāng)于賦值
      this.aaaaaa = val * 2;
    }
};
var childObject = {
    b: 3
};
childObject.__proto__ = parentObject;
childObject.a = 10;
console.log(childObject); //Object {b: 3, aaaaaa: 20}
console.log(parentObject); //Object {}

另外生巡,屬性屏蔽還有一種很容易被忽略的情況??:

var parentObject = {
    a: 2
};

var childObject = Object.create( parentObject ); // 這句話相當(dāng)于先定義一個(gè)空對(duì)象耙蔑,再綁定原型
console.log(parentObject.a); // 2
console.log(childObject.a); // 2
console.log(parentObject.hasOwnProperty('a')); // true
console.log(childObject.hasOwnProperty('a')); // false
console.log(parentObject); // > Object {a:2}

childObject.a++;  // 這時(shí)候迭加的應(yīng)是原型鏈上parentObject的a

console.log(parentObject.a); // 2
console.log(childObject) // > Object { a: 3 }
console.log(childObject.a); // 3

console.log(childObject.hasOwnProperty('a')); // true

childObject.a訪問(wèn)的應(yīng)是parentObject上的a屬性,然而執(zhí)行迭加后卻產(chǎn)生了上面這個(gè)結(jié)果孤荣,原型鏈上的a并沒(méi)有被修改到甸陌。 原因就是,在執(zhí)行childObject.a++時(shí)盐股,發(fā)生了隱式的屬性屏蔽钱豁,因?yàn)?code>childObject.a++實(shí)際上就相當(dāng)于childObject.a = childObject.a + 1

8. 關(guān)于prototype中的constructor屬性

上面有介紹說(shuō)到constructor是函數(shù)原型的一個(gè)屬性疯汁,指向函數(shù)的本身寥院。

function Foo() {
  this.name = 'dog';
}

Foo.prototype.constructor === Foo; // true

var a = new Foo(); 
a.constructor === Foo; // true

當(dāng)a.constructor === Foo的時(shí)候,其實(shí)這時(shí)候并不能夠說(shuō)明a是由Foo構(gòu)造而成的涛目。實(shí)際上秸谢,a.constructor的引用是被委托給了Foo.prototype(本身a自身是沒(méi)有這個(gè)屬性的)凛澎,所以才會(huì)出現(xiàn)等價(jià)的情況,而并不能說(shuō)明a是由Foo構(gòu)造而成的估蹄。

而對(duì)于constructor來(lái)說(shuō)塑煎,這個(gè)屬性其實(shí)就是[[prototype]]上一個(gè)簡(jiǎn)單的默認(rèn)屬性,沒(méi)有writable:false也不是setter臭蚁,只是有一個(gè)默認(rèn)行為最铁。

繼續(xù)看下面的代碼:

function Foo() {
  this.name = 'dog';
}
Foo.prototype = {
  h: 'hhh'
};

var a1 = new Foo();

a1.constructor === Foo; // false
a1.constructor === Object; // true

a1 instanceof Foo //true

這里由于Foo.prototype的默認(rèn)屬性被清空了,所以constructor不存在垮兑,可是__proto__構(gòu)成的原型鏈?zhǔn)遣蛔兊睦湮荆?code>a1.constructor的引用被委托到Object.prototype.constructor,所以第一個(gè)返回false系枪,第二個(gè)返回true雀哨。

所以,我們應(yīng)該怎么對(duì)待constructor這個(gè)屬性呢??私爷?

它并不是什么神秘的屬性雾棺,Foo.prototypeconstructor屬性只是Foo函數(shù)在聲明時(shí)的默認(rèn)屬性。一定程度上可以用.constructor來(lái)判斷原型指向衬浑,但它并不安全捌浩,除了有這個(gè)默認(rèn)行為之外,<b>它和我們平常自定義的屬性工秩,再也沒(méi)什么區(qū)別了尸饺。</b>

9. 當(dāng)我們?cè)谑褂?code>new的時(shí)候到底發(fā)生了什么

JavaScript中,構(gòu)造函數(shù)只是一些使用new操作符時(shí)被調(diào)用的函數(shù)助币,它們并不會(huì)屬于某個(gè)類侵佃,也不會(huì)實(shí)例化一個(gè)類。所以奠支,實(shí)際上并不存在所謂的“構(gòu)造函數(shù)”馋辈,只有對(duì)于函數(shù)的“構(gòu)造調(diào)用”。

當(dāng)使用new來(lái)調(diào)用函數(shù)時(shí)倍谜,會(huì)自動(dòng)執(zhí)行以下操作:

  • 創(chuàng)建一個(gè)全新的對(duì)象
  • 這個(gè)新對(duì)象會(huì)被執(zhí)行[[prototype]]連接
  • 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的this
  • 如果函數(shù)沒(méi)有返回其他對(duì)象迈螟,那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。

看下面的例子:

function SuperType(name) { // 定義了一個(gè)超類尔崔,供下面的子類繼承
    this.name = name;
}

function SubType() { // 定義了子類1答毫,繼承了超類,無(wú)返回值
    SuperType.call(this, "Cong1");
    this.age = 29;  
}

function SubType2() { // 定義了子類2季春,繼承了超類洗搂,返回了一個(gè)引用類型的值
    SuperType.call(this, "Cong2");
    this.age = 29;
    return { a: 2 };
}

function SubType3() { // 定義了子類3,繼承了超類,返回了一個(gè)值類型的值
    SuperType.call(this, "Cong3");
    this.age = 29;
    return 3;
}
/* 下面比較有new操作符和無(wú)new操作符調(diào)用子類的區(qū)別 */

var instance1_nonew = SubType();
var instance2_nonew  = SubType2();
var instance3_nonew = SubType3();
var instance1_hasnew = new SubType();
var instance2_hasnew = new SubType2();
var instance3_hasnew = new SubType3();


// 依次打印六個(gè)變量
console.log(…);

得到的結(jié)果是:

instance1_nonew
undefined
instance2_nonew
> Object {a: 2}
instance3_nonew
3
instance1_hasnew
> SubType {name: "Cong1", age: 29}
instance2_hasnew
> Object {a: 2}
instance3_hasnew
> SubType3 {name: "Cong3", age: 29}

沒(méi)有new操作符的語(yǔ)句耘拇,就像我們平常調(diào)用函數(shù)一樣撵颊,得到的肯定是函數(shù)的返回值,所以前3個(gè)_nonew變量就會(huì)得到圖示所示的結(jié)果惫叛。

而看到下面3個(gè)_hasnew變量倡勇,行為卻有點(diǎn)不同,沒(méi)有返回值的1_hasnew就直接構(gòu)造了一個(gè)實(shí)例對(duì)象嘉涌,而2_hasnew3_hasnew都是有返回值的妻熊,兩者的表現(xiàn)卻不同了。

根據(jù)上面所說(shuō)的原理再來(lái)分析一下這個(gè)過(guò)程:

  1. 首先新建一個(gè)對(duì)象:
    var instance = new Object()
  2. 給這個(gè)對(duì)象設(shè)置[[prototype]]鏈:
    instance.__proto__ = SubType.prototype
  3. 綁定this仑最,將SubType中的this指向instance扔役,執(zhí)行SubType中的語(yǔ)句進(jìn)行賦值。
  4. 返回值警医,這里要根據(jù)SubType的返回類型來(lái)判斷??:
  • 如果是一個(gè)引用類型(對(duì)象)亿胸,那么就替換掉instance本身的這個(gè)對(duì)象。(如:instance2_hasnew
  • 如果是值類型法严,那么直接丟棄它损敷,返回instance對(duì)象本身葫笼。(如:instance3_hasnew

10. 應(yīng)用:兩種繼承的設(shè)計(jì)模式

在JavaScript中沒(méi)有類的概念深啤,使用的是原型繼承。而有兩種常見(jiàn)的設(shè)計(jì)模式路星,一種是面向?qū)ο竽J剿萁郑硗庖环N是對(duì)象關(guān)聯(lián)模式。

在使用的過(guò)程中洋丐,都用到了Object.create()呈昔,它會(huì)創(chuàng)建一個(gè)新對(duì)象并把它關(guān)聯(lián)到我們指定的對(duì)象,也就是進(jìn)行[[prototype]]連接友绝。

  • “原型”面向?qū)ο箫L(fēng)格
function Foo(who) {
    this.me = who
}
Foo.prototype.identify = function() {
    return "I am " + this.me
}
function Bar(who) {
    Foo.call(this,who)
}
Bar.prototype = Object.create(Foo.prototype)

Bar.prototype.speak = function() {
    console.log("Hello, " + this.identify() + ".")
}

var b1 = new Bar("b1")
var b2 = new Bar("b2")

b1.speak() //Hello, I am b1.
b2.speak() //Hello, I am b2.

關(guān)系圖如下:

  • 對(duì)象關(guān)聯(lián)風(fēng)格
Foo = {
    init: function(who) {
        this.me = who
    },
    identify: function() {
        return "I am " + this.me
    }
}
Bar = Object.create(Foo)
Bar.speak = function() {
    console.log("Hello, " + this.identify() + ".")
}

var b1 = Object.create(Bar)
b1.init("b1")
var b2 = Object.create(Bar)
b2.init("b2")

b1.speak() //Hello, I am b1.
b2.speak() //Hello, I am b2.

關(guān)系圖如下:

以上兩種繼承的設(shè)計(jì)堤尾,明顯發(fā)現(xiàn)第二種更加的簡(jiǎn)潔。
在“原型面向?qū)ο箫L(fēng)格”中迁客,需要時(shí)刻的留意prototype的情況郭宝,[[prototype]]“游走”于函數(shù)的prototype之間。
而對(duì)于“對(duì)象關(guān)聯(lián)風(fēng)格”掷漱,它只關(guān)心一件事粘室,那就是對(duì)象之間的關(guān)聯(lián)情況,不將方法寫(xiě)于函數(shù)的prototype上卜范。

雖然實(shí)現(xiàn)的原理是相同的衔统,但是不同的思維方式,更利于理解,代碼風(fēng)格更為友好??锦爵。

11. 函數(shù)與對(duì)象到底是什么關(guān)系

其實(shí)舱殿,這個(gè)問(wèn)題也是困擾了我很久??。

我們都知道:

  1. 一切對(duì)象繼承于Object棉浸。(當(dāng)然Object.prototype除外)
  2. Object.prototype.__proto__指向了null怀薛。
  3. 對(duì)象都是由函數(shù)創(chuàng)建的。

以上迷郑,看似并沒(méi)有什么用枝恋,那現(xiàn)在我們來(lái)縷一下思路。

  1. (這里先不考慮Object.prototype)一切對(duì)象繼承于Object 嗡害,所以說(shuō)焚碌,對(duì)象的原型鏈(__proto__)最終的位置應(yīng)該是Object.prototype。所以一切的老大應(yīng)該是Object.prototype霸妹。
  2. Object.prototype.__proto__指向了null十电。既然__proto__的指向是創(chuàng)建這個(gè)對(duì)象的函數(shù)原型,可是這里Object.prototype.__proto__卻指向了null叹螟。那么鹃骂,唯一可能就是Object.prototype是由JavaScript引擎創(chuàng)造出來(lái)的。
  3. 所以罢绽,<b>最終[[prototype]]鏈的位置應(yīng)該是null而不是Object.prototype</b>牧挣。
  4. 對(duì)象都是由函數(shù)創(chuàng)建的轴总。(這里的對(duì)象同樣是不考慮Object.prototype的)也就是說(shuō),所有的對(duì)象都是由Function構(gòu)造出來(lái),那么他們的[[prototype]]都應(yīng)該經(jīng)過(guò)Function.prototype耘婚。
  5. 于是皿伺,引用類型等構(gòu)造函數(shù)(如:Array()神得、Object()等)以及普通的函數(shù)對(duì)象频轿,甚至Function,他們的__proto__應(yīng)該是指向Function.prototype痊银。
  6. Function.prototype__proto__指向了哪里抵蚊?由第一點(diǎn)可知,當(dāng)然是指向了Object.prototype溯革,所以Function.prototype就是老二贞绳。

所以,簡(jiǎn)而言之:

  • 首先有的應(yīng)該是Object.prototype鬓照,它是由JavaScript引擎創(chuàng)造出來(lái)的熔酷。
  • 緊接著才有Function.prototype,并把它的__proto__連接到了Object.prototype豺裆。
  • 接下來(lái)拒秘,將各種內(nèi)置引用類型的構(gòu)造函數(shù)的__proto__連接到了Function.prototype号显。
  • 執(zhí)行Function.__proto__連接到Function.prototype的操作。
  • 執(zhí)行Object.__proto__連接到Object.prototype的操作躺酒。
  • 最后再是對(duì)FunctionObject實(shí)例的掛載押蚤。

注:以上為個(gè)人的見(jiàn)解,歡迎指正??羹应。


??這是一條可愛(ài)的分割線??揽碘。

以上,就是本次博客的全部?jī)?nèi)容(終于結(jié)束了)感謝你耐心的閱讀??
第一次寫(xiě)博客园匹,如有理解錯(cuò)誤的地方雳刺,師請(qǐng)改正??。

參考資料:
書(shū)籍:《你不知道的JavaScript(上卷)》
博客:http://www.cnblogs.com/wangfupeng1988/p/4001284.html
博客:http://www.yangzicong.com/article/1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末裸违,一起剝皮案震驚了整個(gè)濱河市掖桦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌供汛,老刑警劉巖枪汪,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異怔昨,居然都是意外死亡雀久,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)趁舀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赖捌,“玉大人,你說(shuō)我怎么就攤上這事赫编⊙舱海” “怎么了奋隶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵擂送,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我唯欣,道長(zhǎng)嘹吨,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任境氢,我火速辦了婚禮蟀拷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萍聊。我一直安慰自己问芬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布寿桨。 她就那樣靜靜地躺著此衅,像睡著了一般强戴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挡鞍,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天骑歹,我揣著相機(jī)與錄音,去河邊找鬼墨微。 笑死道媚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翘县。 我是一名探鬼主播最域,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锈麸!你這毒婦竟也來(lái)了羡宙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掐隐,失蹤者是張志新(化名)和其女友劉穎狗热,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體虑省,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匿刮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了探颈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熟丸。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖伪节,靈堂內(nèi)的尸體忽然破棺而出光羞,到底是詐尸還是另有隱情,我是刑警寧澤怀大,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布纱兑,位于F島的核電站,受9級(jí)特大地震影響化借,放射性物質(zhì)發(fā)生泄漏潜慎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一蓖康、第九天 我趴在偏房一處隱蔽的房頂上張望铐炫。 院中可真熱鬧,春花似錦蒜焊、人聲如沸倒信。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鳖悠。三九已至唆迁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間竞穷,已是汗流浹背唐责。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘾带,地道東北人鼠哥。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像看政,于是被迫代替她去往敵國(guó)和親朴恳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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