在這篇文章里我們將要了解以下幾個(gè)方面:
- 關(guān)于內(nèi)存的那點(diǎn)事兒
- 關(guān)于垃圾回收那點(diǎn)事兒
- 包裝對(duì)象
- 什么是原型
- 什么是原型鏈
- __ proto __和prototype
一嫩痰、關(guān)于內(nèi)存那點(diǎn)事兒
我們經(jīng)常會(huì)想么抗,當(dāng)我們聲明并賦值一個(gè)變量時(shí)干奢,存儲(chǔ)的狀態(tài)是如何的,我們?cè)傩姓{(diào)用時(shí)又是一套什么操作呢鹅颊?
我們來假設(shè)一下纵竖,如果計(jì)算機(jī)內(nèi)存有2G,開機(jī)后在旱,操作系統(tǒng)分配到了512M摇零,瀏覽器分配到了1G,假設(shè)除去各種頁面html等等內(nèi)存分配外桶蝎,JS分配到了100M驻仅,那這100M如何分配?
是這樣的登渣,它劃分了兩個(gè)區(qū)噪服,我們簡(jiǎn)單稱其為代碼區(qū)和數(shù)據(jù)區(qū),代碼區(qū)來存儲(chǔ)代碼胜茧,數(shù)據(jù)區(qū)來存儲(chǔ)賦值的數(shù)據(jù)粘优。
比如下列代碼:
var a = 1
// 代碼區(qū) 存儲(chǔ)a
// 數(shù)據(jù)區(qū) 存儲(chǔ)1
代碼區(qū)和數(shù)據(jù)區(qū)又存在著引用關(guān)系,比如我們只要訪問a呻顽,就可以讀取到1.
在JS中雹顺,每一個(gè)數(shù)據(jù)都需要一個(gè)內(nèi)存空間。內(nèi)存空間又被分為兩種廊遍,棧內(nèi)存(stack)與堆內(nèi)存(heap)嬉愧。
數(shù)據(jù)區(qū)內(nèi)也分了兩個(gè)區(qū),棧(stack)內(nèi)存和堆(heap)內(nèi)存喉前,如下圖所示:
我們接下來就看一下數(shù)據(jù)區(qū)没酣。
繼續(xù)重溫7種數(shù)據(jù)類型:
Number,String卵迂,Boolean裕便,null,undefined:簡(jiǎn)單類型數(shù)據(jù)存在stack內(nèi)存中
Object:復(fù)雜類型數(shù)據(jù)存儲(chǔ)在heap內(nèi)存中
JS里所有的數(shù)字都是以64位浮點(diǎn)數(shù)儲(chǔ)存的见咒,16位存儲(chǔ)一個(gè)字符偿衰,所以在棧內(nèi)存內(nèi)都是64位01
我們來看看代碼:
//代碼
var a = 1
var b = 2
var c = true
// 代碼區(qū) //stack
a 0000…1…000000(64位浮點(diǎn)數(shù))
b 0000…10…000000(64位浮點(diǎn)數(shù))
c 100000000000……(64位浮點(diǎn)數(shù))
假設(shè)b=a,那就把a(bǔ)存的東西復(fù)制然后覆蓋到b儲(chǔ)存的地方。
再復(fù)雜一點(diǎn)改览,我們存儲(chǔ)復(fù)雜類型呢哎垦?也就是heap內(nèi)存里是怎樣呢?
// 代碼
var o = {
name:'vava'
age:18
}
o.gender = female
var o2 = {
name:'yaya'
}
// 代碼區(qū) //stack //heap
o ADDR 88 88:name:'vava'
age:18
gender:'female'
o2 ADDR 26 26:name:'yaya'
就像上述代碼恃疯,當(dāng)需要存儲(chǔ)字符的時(shí)候漏设,一行64位浮點(diǎn)數(shù),只能存儲(chǔ)4個(gè)字符今妄,非常浪費(fèi)且再添加屬性的時(shí)候就需要整體移動(dòng)下位的代碼郑口,很麻煩鸳碧,所以我們就在棧內(nèi)存里存儲(chǔ)一個(gè)地址,地址隨意犬性,但是這個(gè)地址指向heap內(nèi)存里相應(yīng)地址的位置瞻离,而我們需要儲(chǔ)存的內(nèi)容就寫在這里。
如果上述代碼內(nèi)乒裆,令o2=o, 那么同理套利,復(fù)制o的存儲(chǔ)內(nèi)容覆蓋到o2,也就是,o2的地址26被88覆蓋鹤耍,o2指向88肉迫,將引用88的內(nèi)容。
上述代碼即下圖:
ps:沒有什么是畫個(gè)圖解決不了的 ~ 遇到內(nèi)存空間問題稿黄,放心大膽的畫圖看看吧~
二喊衫、關(guān)于垃圾回收(GC)的那點(diǎn)事兒
1、Garbage Collection
我們已經(jīng)知道程序的運(yùn)行需要內(nèi)存杆怕。但是如果我們不再用到的內(nèi)存呢族购?如果不釋放,內(nèi)存占用越來越高陵珍,輕則影響系統(tǒng)性能寝杖,重則導(dǎo)致進(jìn)程崩潰。所以我們頁面用完就要釋放內(nèi)存互纯,讓其重新分配瑟幕。
如果一個(gè)對(duì)象沒有被引用,它就是垃圾伟姐,就會(huì)被回收。
// 代碼
var a = {name:'vava'}
var b = {name:'haha'}
// 代碼區(qū) // stack //Heap
a ADDR 67 67:vava
b ADDR 17 17:haha
// 當(dāng)a = b
//代碼區(qū) // stack //Heap
a ADDR 17 (67:vava 被回收)
b ADDR 17 17:haha
可以看到亿卤,一開始a指向地址67愤兵,b指向地址17,當(dāng)a=b排吴,b的地址覆蓋過來秆乳,a和b指向地址17,地址67不再被引用,所以被回收钻哩。
2屹堰、內(nèi)存泄漏:不再用到的內(nèi)存,沒有及時(shí)釋放街氢,就叫做內(nèi)存泄漏(memory leak)
即由于瀏覽器的一些bug扯键,使得該被標(biāo)記為垃圾回收的東西沒有被當(dāng)做垃圾回收,內(nèi)存始終被占用著珊肃,沒有被釋放荣刑。
比如我們有一個(gè)document.body.onclik事件占用著內(nèi)存馅笙,沒有被清除,我們可以如下操作:
window.onunload = function(){
document.body.onclik = null
}
注意厉亏,還有其他事件的話董习,其他事件都要=null。
3爱只、深拷貝V.S淺拷貝
var a = 1
var b = a
b = 2
a
// 1
像這樣皿淋,b改變不會(huì)影響a,這就是深拷貝
對(duì)于所有的基本類型恬试,賦值都是深拷貝窝趣,所以我們來研究對(duì)象。
// 代碼區(qū) // stack //heap
o ADDR 9 9:name:'yaya'
b ADDR 9 // 也指向地址9
如上述忘渔,b.name = 'maya' 高帖,改變name的值,是在地址9中完成畦粮,這時(shí)o和b都指向地址9散址,所以,引用a,得到的也是改變后的宣赔。
像這樣预麸,b的改變會(huì)導(dǎo)致a的改變,就是淺拷貝儒将。
三吏祸、包裝對(duì)象
我們都知道對(duì)象是一種復(fù)合值:它是屬性或已命名值得集合。當(dāng)屬性值為一個(gè)函數(shù)時(shí)稱之為方法钩蚊。那么我們來看一下下圖:
我們可以看到字符串也同樣具有屬性和方法贡翘,但字符串既不是對(duì)象,怎么會(huì)有屬性呢砰逻?
其實(shí)只要我們引用上述字符串的屬性鸣驱,JavaScript就會(huì)將字符串值通過調(diào)用new String(s)的方式轉(zhuǎn)換成對(duì)象,之歌對(duì)象繼承了字符串的方法蝠咆,處理屬性的引用踊东。屬性引用結(jié)束,這個(gè)新創(chuàng)建的對(duì)象就被銷毀刚操。
我們可以這樣來想闸翅,我們既想用簡(jiǎn)單類型,又想獲得對(duì)象的方法菊霜,然后Branden Eich就想了個(gè)辦法坚冀,我們可以建立臨時(shí)對(duì)象,獲取屬性后返回給調(diào)用鉴逞,然后銷毀就可以遗菠。
var n = 1
n.toString()
// '1'
// 代碼區(qū) // stack // heap
n 1
temp ADDR 77 77: 1
toSting()
valueOf()
temp就是臨時(shí)對(duì)象联喘,當(dāng)調(diào)用toString返回后,temp馬上被銷毀了辙纬。
其他的數(shù)據(jù)類型也是一樣豁遭,
調(diào)取屬性的背后都是這樣一套操作。
四贺拣、什么是原型
從上一節(jié)包裝對(duì)象我們可以看到蓖谢,利用new創(chuàng)建并初始化一個(gè)新對(duì)象,運(yùn)算符new后面跟著一個(gè)函數(shù)調(diào)用譬涡,叫做構(gòu)造函數(shù)闪幽。
var s = new String('hello')
如上述代碼,s就是被創(chuàng)建的實(shí)例對(duì)象涡匀,new運(yùn)算符后跟著的String(注意這里開頭必須大寫來和string區(qū)分)就是構(gòu)造函數(shù)盯腌。
我們可以看到從實(shí)例對(duì)象中調(diào)用的屬性,但是他們都具有的屬性比如toString以及valueOf等陨瘩,如果每個(gè)實(shí)例對(duì)象都在heap存儲(chǔ)處生成這些都有的屬性豈不是很費(fèi)內(nèi)存腕够?所以我們可以共有屬性歸攏起來,然后通過 __ proto__ 來指向共有屬性。
var o = new Object({name:'vava'})
//代碼區(qū) // stack //heap
o ADDR 89 89: name:vava object:toString()
__proto__:object valueOf()
……
所以在公有屬性調(diào)用時(shí)就是上述操作
通過在console.log打印出來,我們可以看到一個(gè)哈希锤窑,__ proto__指向了object去調(diào)用屬性。
object公有屬性是所有對(duì)象的公有屬性大诸,但是還有一些比如是只有number數(shù)據(jù)類型共同具有的,或者只有String數(shù)據(jù)類型共同具有的呢贯卦?我們來看一下:
明白了么资柔?也是一樣的操作去調(diào)用,只不過實(shí)例對(duì)象通過__ proto__先調(diào)用number公有屬性.如果沒有要調(diào)用的屬性撵割,就繼續(xù)通過__ proto__調(diào)用object公有屬性贿堰。
看到這你可能想問了,說了這么多跟原型有什么關(guān)系呢睁枕?當(dāng)然有了官边,公有屬性就是原型沸手,每一個(gè)對(duì)象都在原型繼承屬性外遇,所有的函數(shù)對(duì)象都具有原型對(duì)象,并且通過JavaScript代碼Object.prototype來獲得對(duì)屬性的引用契吉。
看上圖就可以了解到跳仿,構(gòu)造函數(shù)通過new運(yùn)算符創(chuàng)建了實(shí)例對(duì)象,實(shí)例對(duì)象內(nèi)通過__ proto__指向了原型對(duì)象即Object.prototype.
當(dāng)你聲明了一個(gè)對(duì)象捐晶,JS引擎除了在棧里搞了一個(gè)哈希菲语,還干了一件事妄辩,那就是把__ proto__指向了公有屬性,即原型山上。
五眼耀、什么是原型鏈
原型鏈:每一個(gè)對(duì)象都有自己的原型對(duì)象,原型對(duì)象本身也是對(duì)象佩憾,原型對(duì)象也有自己的原型對(duì)象哮伟,這樣就形成了一個(gè)鏈?zhǔn)浇Y(jié)構(gòu),叫做原型鏈妄帘。
比如下面這個(gè)原型鏈
實(shí)例對(duì)象s---(通過__ proto__)-->String.prototype---(通過__ proto__)---->Object.prototype----(通過__ proto__)----->null
對(duì)這個(gè)實(shí)例化對(duì)象而言楞黄,訪問對(duì)象的屬性,是首先在對(duì)象本身去找抡驼,如果沒有鬼廓,就會(huì)去他的原型對(duì)象中找,一直找到原型鏈的終點(diǎn)null致盟;根據(jù)定義碎税,null沒有原型,并作為這個(gè)原型鏈中的最后一個(gè)環(huán)節(jié)勾邦。
六蚣录、__ proto__和prototype
var 對(duì)象 = new 函數(shù)() , 故而
對(duì)象.__ proto__ === 函數(shù).prototype
__ proto __是實(shí)例對(duì)象的公有屬性引用眷篇,prototype是函數(shù)對(duì)象公有屬性的引用萎河。
1、函數(shù).prototype.__ proto __ ===Object.prototype
這里我們循著原型鏈就可以理解蕉饼,當(dāng)函數(shù)是Object虐杯,它的原型對(duì)象自然指向了原型鏈的終點(diǎn)null。
2昧港、函數(shù).__ proto __ === Function.prototype
Function是Object的構(gòu)造函數(shù)擎椰,所以函數(shù)Object或者函數(shù)Number等等,他們的__ proto __ 都指向了Function.prototype
從上圖中有清晰的對(duì)比创肥,實(shí)例對(duì)象o的__ proto __ 就指向了構(gòu)造實(shí)例對(duì)象函數(shù)的原型對(duì)象达舒,即Object.prototype.而函數(shù)Object的__ proto __ 就指向了它的構(gòu)造函數(shù)Function.prototype.
所以接下來兩個(gè)推論我們也很容易理解了:
3、Function.__ proto __ === Function.prototype
4叹侄、Function.prototype.__ proto __ === Object.prototype
(這里Function.prototype是對(duì)象巩搏,原型對(duì)象)
tips:
1、每一個(gè)構(gòu)造函數(shù)都擁有一個(gè)prototype屬性趾代,這個(gè)屬性指向一個(gè)對(duì)象贯底,也就是原型對(duì)象。當(dāng)使用這個(gè)構(gòu)造函數(shù)創(chuàng)建實(shí)例的時(shí)候撒强,prototype屬性指向的原型對(duì)象就成為實(shí)例的原型對(duì)象禽捆。
2笙什、原型對(duì)象默認(rèn)擁有一個(gè)constructor屬性,指向指向它的那個(gè)構(gòu)造函數(shù)(也就是說構(gòu)造函數(shù)和原型對(duì)象是互相指向的關(guān)系)胚想。
3琐凭、每個(gè)對(duì)象都擁有一個(gè)隱藏的屬性[[prototype]],指向它的原型對(duì)象浊服,這個(gè)屬性可以通過Object.getPrototypeOf(obj)
或obj.__proto__
來訪問淘正。
4、實(shí)際上臼闻,構(gòu)造函數(shù)的prototype屬性與它創(chuàng)建的實(shí)例對(duì)象的[[prototype]]屬性指向的是同一個(gè)對(duì)象鸿吆,即對(duì)象.__proto__ === 函數(shù).prototype
。
5述呐、如上文所述惩淳,原型對(duì)象就是用來存放實(shí)例中共有的那部分屬性。
6乓搬、在JavaScript中思犁,所有的對(duì)象都是由它的原型對(duì)象繼承而來,反之进肯,所有的對(duì)象都可以作為原型對(duì)象存在激蹲。
7、訪問對(duì)象的屬性時(shí)江掩,JavaScript會(huì)首先在對(duì)象自身的屬性內(nèi)查找学辱,若沒有找到,則會(huì)跳轉(zhuǎn)到該對(duì)象的原型對(duì)象中查找环形。
就到這里啦~ 歡迎糾錯(cuò) ~