Why???
在上面寫了一篇博文介紹了普通類型和對象的區(qū)別。下面就深究一下原理。
公用屬性(原型)
什么是公共屬性?
__proto__:object //__proto__這個屬性不用寫就有,每一個對象的 __proto__存儲「公用屬性組成的對象」的地址。
所有對象都有 toString 和 valueOf 屬性,那么我們是否有必要給每個對象一個 toString 和 valueOf 呢?
看圖理解JS標(biāo)準(zhǔn)庫里幾個構(gòu)造函數(shù)之間的關(guān)系

每個對象都有toString()和valueOf()函數(shù),如果每個對象里都存這樣的相同的函數(shù),那會浪費內(nèi)存.
問題解決方法:原型鏈
把toString()和valueOf()等函數(shù)單獨存放在一個對象里,原來對象里的toString()和valueOf()只存地址,指向這個地址


JS 的做法是把 toString 和 valueOf 放在一個對象里(暫且叫做公用屬性組成的對象)
然后讓每一個對象的 __proto__存儲這個公用屬性組成的對象的地址。


通過四個全局對象來了解原型和原型鏈
這四個全局對象為:Number()、String()、Boolean()、Object()。
上述四個對象都可以通過下邊兩種方法實現(xiàn):
var n1=1
var n2=new Number(1)
var s1='sss'
var s2=new String('sss')
var b1=true
var b2=new Boolean(true)
var o1={}
var o2=new Object()
他們的區(qū)別在于內(nèi)存:
1.toString()會報錯,但是這里我們通過n1.toString()返回的就是字符串'1'。n1.toString()方法不會報錯,但是n1只是通過賦值的方法的來的,并沒有.toString()方法。這個時候就是JS通過創(chuàng)建臨時對象tempvar temp=new Number(1)在這個臨時對象中對n1的值進(jìn)行.toString()方法,再將得到的返回給n1.toString(),臨時對象用完就自動抹殺,被垃圾回收。
在上面的博文中,曾寫過:我們對n1進(jìn)行n1.xxx=2這會不會報錯,結(jié)果是不會報錯。原因就是用臨時方法在heap內(nèi)存上創(chuàng)建的,但是再取n1.xxx時為undefined。就驗證了用完就抹殺,即不存在的原理。
為什么new Number()有.toString()方法呢?我們打印出n2看看他有沒有這個屬性。

可以看到除了打印出n2的值還有一個__proto__:Number,點開這個屬性可以看到很多屬性,其中就包括.toString(),所以n2可以調(diào)用.toString()方法。
__proto__:Number所表示的就是number共有的屬性。
從上面的圖中發(fā)現(xiàn),在這個__proto__:Number中還有一個__protp__屬性。


此時在圖中我們看到n2.__proto__===Number.prototype的結(jié)果為true。就是指n2的__proto__指向了Number.prototype,即Number的共有屬性中去,可以從第一張圖中可以看到n2的__proto__中還有一個__proto__:Object的隱藏屬性。點開就可以看到關(guān)于Object的共有屬性。
根據(jù)同樣的方法,依次獲取String,Boolean,Object,發(fā)現(xiàn):


綜上總結(jié):
以上四個全局函數(shù)構(gòu)造的對象都有一些公有的屬性,比如:toString()、valueOf()。不可能每聲明一個對象都在對象里加上這些公有屬性,這樣內(nèi)存太浪費,Js的做法是把共用屬性放在一起,然后每個對象都有一個鍵名為proto的鍵,它的值就是這些共用屬性所在的內(nèi)存地址。
但是Number、String和Boolean這三個全局函數(shù)都有一些自己的屬性,比如Number函數(shù)有toFixed()、toExponential()、toString(16)等等這些只有Number函數(shù)才有的屬性,并不屬于Object共有的屬性。所以Js的方法是在Number對象的proto中放Number共有屬性的地址,在Number共有屬性對象的proto中放對象共有屬性的地址,對象Object的proto中放的是Null,如下圖所示:

我們可以總結(jié)出來,當(dāng)我們new出來一個對象的時候,再去使用一個方法時,會首先從它自身的構(gòu)造函數(shù)(或者系統(tǒng)自帶函數(shù))的原型對象(函數(shù).prototype)中去找,如果沒有改屬性是就會通過__proto__屬性去下一個Object共有屬性中去尋找,直到最后為null。

var n = new Number();
n.__proto__===Number.prototype//true
n.__proto__.__proto__===Object.prototype//true
n.__proto__.__proto__.__proto__===null//true
流程是這樣的
- 瀏覽器先看n是不是對象,不是做臨時轉(zhuǎn)換。
- 是的話就先去看看n對應(yīng)的函數(shù)公共類型里找有沒有toString()這個操作符
- 如果沒有,就進(jìn)入prote對應(yīng)的公共屬性里找有沒有toString()操作符
- 有的話,就調(diào)用這個toString()
這樣的,形成的穿過多個節(jié)點的流程,就叫原型鏈
原型===共有屬性

當(dāng)解析一句代碼時,如果這個不是對象,就包裝成一個對象,自動生成__proto__。
所有對象的公有屬性(也叫原型): Object.prototype;
所有number的公有屬性(也叫原型): Number.prototype
所有string的公有屬性(也叫原型): String.prototype
所有boolean的公有屬性(也叫原型): Boolean.prototype

prototype和__proto__的區(qū)別:前者的函數(shù)的屬性,后者是對象的屬性。
__proto__與prototype的關(guān)系
[__proto__指向prototype]
內(nèi)存圖理解
var o1 = new Number(8)
var o2 = 8
o1 === o2 // false
o1.toString === o2.toString // true
因為他們調(diào)用的方法是一樣的

o1 是一個對象,在內(nèi)存中的地址是125
o2 不是對象,是Number類型的
所以在第三行語句中 o1 ===o2 答案是
false但是在第四行的時候,o1 使用了
o1.toString()的時候,他會自動生成一個temp的對象,并且去調(diào)用。所以它會在內(nèi)存中生成一個地址為50的對象,但是因為125地址和50地址都是共有屬性中的toString() ,所以最后他們的結(jié)果都是true
重要公式-原型與原型鏈
無代碼的時候,即為下面這樣,瀏覽器已經(jīng)將其初始化好了.

可以看到prototype是用來指向這些共有屬性的,不然這些共有屬性就被垃圾回收了,所以要用一個線來牽引著.
寫代碼之后

在看這張圖前,我們先考慮一下瀏覽器的垃圾回收機制,垃圾回收會回收沒有被引用的對象。
這些全局對象如果不被引用的話,就會被瀏覽器回收掉,如何避免這些全局屬性被回收掉呢?
瀏覽器在打開的時候,就會創(chuàng)建一個名為window的全局屬性,這個全局屬性包含了所有全局函數(shù)的地址值。
這些地址又引用了該函數(shù)特有的公共屬性(對象),每個函數(shù)特有的公共屬性(對象)又用_prote_來儲存Object對象的地址
所以:
-
prototype是瀏覽器開始就準(zhǔn)備好了的,用來防止共有屬性被垃圾回收的, -
__proto__是在開始寫代碼的時候用來引用共有函數(shù)的. -
String.prototype是String的公用屬性的引用,是JS在初始化的時候就已經(jīng)存在的,用他是因為如果不用他,那么公用屬性就跑了,被垃圾回收了
*'s'.__proto__是String的公用屬性的引用,是在聲明新對象的時候存在的,有他是因為我要用他,用公用屬性
共同點就是都是公共屬性的引用.
var o1 = {};
o1.__proto__ === Object.prototype//true
Number.prototype.__proto__===Object.prototype//true
var s1 = new String('s1');
s1.__proto__ === String.prototype//true

String.prototype.__proto__ ===Object.prototype//true
function DOG(name){
this.name = name;
}
DOG.prototype.__proto__ === Object.prototype//true
對象與構(gòu)造函數(shù)

var 對象 = new 函數(shù)()
對象.__proto__ === 對象的構(gòu)造函數(shù).prototype
形式
總結(jié)的式子
由此,我們可以得出一個公式,結(jié)合原型解釋圖理解

var number = new Number()
number.__proto__ = Number.prototype
Number.__proto__ = Function.prototype // 因為 Number 是 Function 的實例
var object = new Object()
object.__proto__ = Object.prototype
Object.__proto__ = Function.prototype // 因為 Object 是 Function 的實例
var function = new Function()
function.__proto__ = Function.prototype
Function.__proto__ == Function.prototye // 因為 Function 是 Function 的實例!


只有函數(shù)才能有prototype
兩個屬性對比:
共同點:存的地址相同,都指向同一個對象
不同點:一個是對象的屬性,一個是函數(shù)的屬性
面試題
-
'1'.__proto__
答:'1'會創(chuàng)建一個臨時String對象,然后指向String.prototype -
函數(shù).prototype是一個對象,那么
var obj = 函數(shù).prototype;
obj.__proto__ === Object.prototype;//true
函數(shù).prototype.__proto__ === Object.prototype;//true
成立(可以看上圖無代碼的時候)
Number.prototype.__proto__ === Object.prototype
//true
String.prototype.__proto__ === Object.prototype
//true
Boolean.prototype.__proto__ === Object.prototype
//true
JS 原型是什么?
答:舉例
var a = [1,2,3]
只有0、1、2、length 4 個key
為什么可以 a.push(4) ,push 是哪來的?
a.__proto__ === Array.prototype(a是實例數(shù)組對象,Array是構(gòu)造函數(shù))
push函數(shù) 就是沿著 a.__proto__找到的,也就是 Array.prototype.push
Array.prototype 還有很多方法,如 join、pop、slice、splice、concat
Array.prototype 就是 a 的原型(proto)
舉完例子后用new對象舉例,說給面試官聽:
比如說
- 我們新創(chuàng)建一個構(gòu)造函數(shù)
function Person() {}
- 然后根據(jù)構(gòu)造函數(shù)構(gòu)造一個新對象
var person1 = new Person();
- 每個函數(shù)都有一個 prototype 屬性,這個構(gòu)造函數(shù)的 prototype 屬性指向了一個對象,這個對象正是調(diào)用該構(gòu)造函數(shù)而創(chuàng)建的實例的原型。
- 當(dāng)我們給Person的prototype的name屬性賦值為'Kevin'
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin
每一個新的實例對象對象都會從原型"繼承"屬性,實例對象擁有該原型的所有屬性。
- 說白了,原型就是 構(gòu)造函數(shù) 用來 構(gòu)造 新實例 的 模板對象。
- 這就是原型。
開始解釋原型鏈
那么我們該怎么表示實例與實例原型,也就是 person1 和 Person.prototype 之間的關(guān)系呢,這時候我們就要講到第二個屬性__proto__。
什么是原型鏈?
參考這篇博文什么是 JS 原型鏈?
先回答什么是原型。在上面,然后繼續(xù)從proto開始往下說。
說:
JavaScript對象除了 null 都具有的一個屬性,叫__proto__,這個屬性會指向該對象的原型對象。
當(dāng)讀取實例的屬性時,如果找不到,就會通過__proto__查找原型中的屬性,如果還查不到,就去找原型的原型。
例如Person.prototype這個原型的原型就是Object這個構(gòu)造函數(shù)的prototype,既Object.prototype這個原型對象。然后,Person.prototype.__proto__就指向Object.prototype這個原型。然后Object.prototype原型是null。
這些原型對象通過__proto__像鏈子一樣連起來,就叫做原型鏈。
然后給面試官畫:
鏈子上都畫上__proto__
person1----->Person.prototype----->Object.prototype----->null
Array.prototype----->Object.prototype----->null