javascript
JavaScript学习笔记 - 变量、作用域与内存问题
本文記錄了我在學(xué)習(xí)前端上的筆記,方便以后的復(fù)習(xí)和鞏固。
4.1基本類型和引用類型的值
ECMAScript變量可能包含兩種不同數(shù)據(jù)類型的值:基本類型值和引用類型值。基本類型指的是簡單的數(shù)據(jù)段,而引用類型值指那些可能由多個值構(gòu)成的對象。
數(shù)據(jù)類型:
基本類型值:Undefined、Null、Boolean、Number、String;
引用類型值,也就是對象類型:Object、Array、Function、Date等;
聲明變量時不同的內(nèi)存分配
基本類型值:存儲在棧(stack)中的簡單數(shù)據(jù)段,它們的值直接存儲在變量訪問的位置。這是因為這些基本類型占據(jù)的空間是固定的,所以可以將它們存儲在較小的內(nèi)存區(qū)域 - 棧中。這樣存儲更便于迅速查尋變量的值。
引用值:存儲在堆(heap)中的對象,也就是說,存儲在變量處的值是一個指針(point),指向存儲對象的內(nèi)存地址。這是因為:引用值的大小會改變,所以不能把它放在棧中,否則會降低變量查尋的速度。相反,放在變量的棧空間中的值是該對象存儲在堆中的地址。地址的大小是固定的,所以把它存儲在棧中對變量性能無任何負(fù)面影響。
不同的內(nèi)存分配機制也帶來了不同的訪問機制
在javascript中是不允許直接訪問保存在堆內(nèi)存中的對象的,也就是說不能直接操作對象的內(nèi)存空間。所以在訪問一個對象時,首先得到的是這個對象在堆內(nèi)存中的地址,然后再按照這個地址去獲得這個對象中的值,這就是傳說中的按引用訪問。而原始類型的值則是可以直接訪問到的。
注意:當(dāng)復(fù)制保存著對象的某個變量時,操作的事對象的引用。但在為對象添加屬性時,操作的是實際的對象
復(fù)制變量的不同
基礎(chǔ)類型值:在將一個保存著基礎(chǔ)類型值的變量復(fù)制給另一個變量時,會將原始值的副本賦值給新變量,此后這兩個變量是完全獨立的,他們只是擁有相同的value而已。
引用值:在將一個保存著對象內(nèi)存地址的變量復(fù)制給另一個變量時,會把這個內(nèi)存地址賦值給新變量,也就是說這兩個變量都指向了堆內(nèi)存中的同一個對象,他們中任何一個作出的改變都會反映在另一個身上。(這里要理解的一點就是,復(fù)制對象時并不會在堆內(nèi)存中新生成一個一模一樣的對象,只是多了一個保存指向這個對象指針的變量罷了)
參數(shù)傳遞的不同
首先我們應(yīng)該明確一點:ECMAScript中所有函數(shù)的參數(shù)都是按值來傳遞的。但是為什么涉及到基礎(chǔ)類型與引用類型的值時仍然有區(qū)別呢,還不就是因為內(nèi)存分配時的差別。
基礎(chǔ)類型值:只是把變量里的值傳遞給參數(shù),之后參數(shù)和這個變量互不影響。
引用類型值:對象變量它里面的值是這個對象在堆內(nèi)存中的內(nèi)存地址,這一點你要時刻銘記在心!因此它傳遞的值也就是這個內(nèi)存地址,這也就是為什么函數(shù)內(nèi)部對這個參數(shù)的修改會體現(xiàn)在外部的原因了,因為它們都指向同一個對象呀。或許我這么說了以后你對書上的例子還是有點不太理解,那么請看圖吧:
所以,如果是按引用傳遞的話,是把第二格中的內(nèi)容(也就是變量本身)整個傳遞進去(就不會有第四格的存在了)。但事實是變量把它里面的值傳遞(復(fù)制)給了參數(shù),讓這個參數(shù)也指向原對象。因此如果在函數(shù)內(nèi)部給這個參數(shù)賦值另一個對象時,這個參數(shù)就會更改它的值為新對象的內(nèi)存地址指向新的對象,但此時原來的變量仍然指向原來的對象,這時候他們是相互獨立的;但如果這個參數(shù)是改變對象內(nèi)部的屬性的話,這個改變會體現(xiàn)在外部,因為他們共同指向的這個對象被修改了呀!來看下面這個例子吧:(傳說中的call by sharing)
var obj1 = {value:'111' };var obj2 = {value:'222' };function changeStuff(obj){obj.value = '333';obj = obj2; return obj.value; }var foo = changeStuff(obj1);console.log(foo);// '222' 參數(shù)obj指向了新的對象obj2 console.log(obj1.value);//'333'obj1仍然指向原來的對象,之所以value改變了,
是因為changeStuff里的第一條語句,這個時候obj是指向obj1的 .
再啰嗦一句,如果是按引用傳遞的話,這個時候obj1.value應(yīng)該是等于'222'的
可以把ECMAScript函數(shù)的參數(shù)想象成局部變量
4.1.4 檢測類型
如果變量的值是一個對象或null,則typeof操作符會返回"object".
通常我們并不是想知道某個值是對象,而是想知道它是什么類型的對象。為此,ECMAScript提供了instanceof操作符;
如果對象是給定引用類型的實例,那么instanceof操作符就會返回true。
console.log(person instanceof Object); //變量person是Object嗎? console.log(colors instanceof Array); //變量colors是Array嗎? console.log(pattern instanceof RegExp); //變量pattern是RegExp嗎?根據(jù)規(guī)定,所有引用類型的值都是Object的實例。在檢查一個引用類型值和Object構(gòu)造函數(shù)時,instanceof操作符始終會返回true。
4.2執(zhí)行壞境和作用域
每個函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進入一個函數(shù)時,函數(shù)的環(huán)境就會被推入一個環(huán)境棧中。而在函數(shù)執(zhí)行后,棧將其環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境。
每個環(huán)境都有一個與之關(guān)聯(lián)的變量對象,環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數(shù)據(jù)時會在后臺使用它
當(dāng)代碼在一個環(huán)境執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。作用域鏈的用途,是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問
4.2.1 延長作用域鏈
有些語句可以在作用域鏈的前端臨時增加一個變量對象,該變量對象會在代碼執(zhí)行后被移除。有兩種情況下會發(fā)生這種現(xiàn)象。
try-catch 語句中的 catch 塊
with 語句
對 with 來說,將會指定對象添加到作用域鏈中。對 catch 來說,會創(chuàng)建一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。
var oMyself = {sFirstname: "Aidan",sLastName: "Dai" }function create(){var sLastName = "Wen"with(oMyself){//將oMyself作為自己的執(zhí)行環(huán)境sAllName = sFirstname +" " + sLastName;}return sAllName; } var sMyName = create(); console.log(sMyName); //Aidan Dai4.2.2 沒有塊級作用域
對于有塊級作用域的語言來說,for語句初始化變量的表達式所定義的變量,只會存在于循環(huán)的環(huán)境之中。而對于JavaScript來說,由for語句創(chuàng)建的變量i即使在for循環(huán)執(zhí)行結(jié)束后,也依舊會存在于循環(huán)外部的執(zhí)行環(huán)境中。
1. 聲明變量
使用var聲明的變量會自動被添加到最接近的環(huán)境中。在函數(shù)內(nèi)部,最接近環(huán)境的就是函數(shù)的局部環(huán)境;在with語句中,最接近的環(huán)境就是函數(shù)環(huán)境。如果初始化變量時沒有使用var聲明,該變量會自動被添加到全局環(huán)境。
注意:在編寫JavaScript中,不聲明而直接初始化變量時一個錯誤的做法,因為這樣可能會導(dǎo)致意外。在嚴(yán)格模式下,初始化未經(jīng)聲明的變量會導(dǎo)致錯誤。
2.查詢標(biāo)識符
搜索過程從作用域鏈的前端開始,向上逐級查詢與給定名字匹配的標(biāo)識符。如果在局部環(huán)境找到,搜索過程停止,變量就緒。如果在局部環(huán)境中沒有找到該變量名,則繼續(xù)沿作用域向上搜索。搜索過程將一直追溯到全局環(huán)境的變量對象。在全局環(huán)境也沒找到的話則說明該變量尚未聲明。
4.3 垃圾收集
JavaScript具有自動垃圾收集機制,也就是說,執(zhí)行環(huán)境會負(fù)責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存。
JavaScript垃圾回收的機制很簡單:找出不再使用的變量,然后釋放掉其占用的內(nèi)存,但是這個過程不是時時的,因為其開銷比較大,所以垃圾回收器會按照固定的時間間隔周期性的執(zhí)行。
變量生命周期
什么叫不再使用的變量?不再使用的變量也就是生命周期結(jié)束的變量,當(dāng)然只可能是局部變量,全局變量的生命周期直至瀏覽器卸載頁面才會結(jié)束。局部變量只在函數(shù)的執(zhí)行過程中存在,而在這個過程中會為局部變量在棧或堆上分配相應(yīng)的空間,以存儲它們的值,然后再函數(shù)中使用這些變量,直至函數(shù)結(jié)束(閉包中由于內(nèi)部函數(shù)的原因,外部函數(shù)并不能算是結(jié)束
一旦函數(shù)結(jié)束,局部變量就沒有存在必要了,可以釋放它們占用的內(nèi)存。貌似很簡單的工作,為什么會有很大開銷呢?這僅僅是垃圾回收的冰山一角,就像剛剛提到的閉包,貌似函數(shù)結(jié)束了,其實還沒有,垃圾回收器必須知道哪個變量有用,哪個變量沒用,對于不再有用的變量打上標(biāo)記,以備將來回收。用于標(biāo)記無用的策略有很多,常見的有兩種方式
4.3.1 標(biāo)記清除
這是JavaScript最常見的垃圾回收方式,當(dāng)變量進入執(zhí)行環(huán)境的時候,比如函數(shù)中聲明一個變量,垃圾回收器將其標(biāo)記為“進入環(huán)境”,當(dāng)變量離開環(huán)境的時候(函數(shù)執(zhí)行結(jié)束)將其標(biāo)記為“離開環(huán)境”。至于怎么標(biāo)記有很多種方式,比如特殊位的反轉(zhuǎn)、維護一個列表等,這些并不重要,重要的是使用什么策略,原則上講不能夠釋放進入環(huán)境的變量所占的內(nèi)存,它們隨時可能會被調(diào)用的到。
垃圾回收器會在運行的時候給存儲在內(nèi)存中的所有變量加上標(biāo)記,然后去掉環(huán)境中的變量以及被環(huán)境中變量所引用的變量(閉包),在這些完成之后仍存在標(biāo)記的就是要刪除的變量了,因為環(huán)境中的變量已經(jīng)無法訪問到這些變量了,最后,垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。
4.3.2 引用計數(shù)
在低版本IE中經(jīng)常會出現(xiàn)內(nèi)存泄露,很多時候就是因為其采用引用計數(shù)方式進行垃圾回收。引用計數(shù)的策略是跟蹤記錄每個值被使用的次數(shù),當(dāng)聲明了一個變量并將一個引用類型賦值給該變量的時候這個值的引用次數(shù)就加1,如果該變量的值變成了另外一個,則這個值得引用次數(shù)減1,當(dāng)這個值的引用次數(shù)變?yōu)?的時候,說明沒有變量在使用,這個值沒法被訪問了,因此可以將其占用的空間回收,這樣垃圾回收器會在運行的時候清理掉引用次數(shù)為0的值占用的空間。
4.3.3 性能問題
垃圾收集器是周期性運行的,而且如果為變量分配的內(nèi)存數(shù)量很可觀,那么回收工作量也是相當(dāng)大的。在這種情況下,確定垃圾收集的時間間隔是一個非常重要的問題。
事實上,在有的瀏覽器中可以觸發(fā)垃圾收集過程,但我們不建議這樣做。在IE中調(diào)用window.CollectGarbage()方法會立即執(zhí)行垃圾收集。在Opera7及更高版本中,調(diào)用window.opera.collect()也會啟動垃圾收集例程。
4.3.4管理內(nèi)存
確保占用最少的內(nèi)存可以讓頁面獲得更好的性能。而優(yōu)化內(nèi)存占用的最佳方式,就是為執(zhí)行中的代碼只保存必要數(shù)據(jù)。一旦數(shù)據(jù)不再可用,最好通過將其值設(shè)置為null來釋放其引用——這個方法叫做解除引用(dereferencing)。這一做法適用于大多數(shù)全局變量和全局對象屬性。局部變量會在它們離開執(zhí)行環(huán)境時自動被解除引用。
function createPerson(name) {var localPerson = new Object();localPerson.name = name;return localPerson; } var globalPerson = createPerson("Nicholas");//手工解除globalPerson的引用globalPerson = null;4.4 小結(jié)
基本類型值和引用類型值具有以下特點:
基本類型值在內(nèi)存中占據(jù)固定大小的空間,因此被保存在棧內(nèi)存中;
從一個變量向另一個變量復(fù)制基本類型的值,會創(chuàng)建這個值得一個副本;
引用類型的值是對象,保存在堆內(nèi)存中;
包含引用類型值得變量實際上包含的并不是對象本身,而是指向該對象的指針;
從一個變量向另一個變量復(fù)制引用類型的值,復(fù)制的其實是指針,因此兩個變量最終都指向同一個對象;
確定一個值是哪種基本類型可以使用typeof操作符,而確定一個值是哪種引用類型可以使用instanceof操作符。
所有變量(包括基本類型和引用類型)都存在于一個執(zhí)行環(huán)境(也稱為作用域)當(dāng)中,這個執(zhí)行環(huán)境決定了變量的生命周期,以及哪一部分代碼可以訪問其中的變量。
最后,如有錯誤和疑惑請指出,多謝各位大哥
總結(jié)
以上是生活随笔為你收集整理的JavaScript学习笔记 - 变量、作用域与内存问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pl/sql基础练习
- 下一篇: 西数数据战略投资固态硬盘