目錄前端
當一個變量保存了基本數據類型時,此時對於變量的操做(賦值,運算)就是操做這個基本數據的自己,就算是賦值操做,賦值時拷貝後的值與以前的值也是相互獨立互不影響的。數組
var a = 1; var b = a b++; console.log(a); //1 console.log(b); //2
這很是好理解,可是若是一個變量保存的是一個引用類型的數據,例如對象,那麼狀況將會不一樣,這是由於變量保存的並非對象自己,而是其在內存中的地址(指針),因此當對引用類型進行賦值時,雖然也會進行拷貝,可是這個拷貝後的值只是一個指針,它們兩個最終指向的都是對同一個對象的引用,所以便會存在相互影響的問題。瀏覽器
var obj = new Object(); var copyObj = obj; obj.name = 'test'; console.log(obj2.name); //test
用圖表示就是以下關係:
函數
經過具體的事例再來感覺這二者的不一樣:性能
function PersonnelInfo(age, info) { age = 10; info.name = "cheng"; } var info = {}; var age = 0; PersonnelInfo(age, info); console.log(age); console.log(info);
由此咱們能夠得出變量的訪問有兩種,一種是「按值訪問」,另外一種則是「按引用訪問」,按值訪問操做變量操做的就是值的自己,在進行賦值的時候也是相互獨立互不影響的,而按引用訪問,再進行賦值的時候,實際上多個變量訪問的對象依然是同一個。ui
通常來講咱們會將固定大小的值(例如基本數據類型)保存到棧內存中,將不能固定大小的值(例如對象,數組等)保存到堆內存中,這樣的區分也是更好的利用內存空間,提升執行效率。編碼
變量的訪問有兩種,「按值訪問」以及「按引用訪問」,而執行環境(Execution Context) 則造成了做用域,做用域又肯定了變量或函數是否有權訪問其它環境中的數據。也就是說執行環境肯定了某些標識符是否能被訪問。url
每一個執行環境都會有個與之對應的變量對象(Variable Object),變量對象用於管理和保存當前環境中的變量以及函數(標識符)。當從一個執行環境進入到另外一個執行環境時,首先便會建立變量對象,而後該執行環境會被加入到當前的執行棧中進行執行,若是執行完畢則會從執行棧中彈出,而且該環境中的代碼、函數以及變量也都會被銷燬。spa
在JavaScript中執行環境主要的有兩種:全局執行環境,函數執行環境。全局執行環境綁定在 WEB瀏覽器頂層宿主對象 window上,也就是說咱們在全局執行環境中聲明的變量或者函數都會將做爲 window對象的屬性或者是方法,也所以只有在退出瀏覽器或關閉WEB頁面纔會銷燬全局執行環境。
函數的執行環境也是一種局部執行環境,變量對象會比較特殊,咱們更多的將其稱之爲「活動對象(Active Object)」,它默認保存的一個標識符就是 arguments
,而 arguments
中保存的即是該函數的參數。debug
當執行環境被加入到執行棧中進行執行的時候,JS引擎會根據變量對象來解析標識符,首先它會查找當前執行環境中的標識符是否在變量對象中有定義,若是有則取得標識符的值進行下一步操做,若是沒有,則向上進入到上一級執行環境,訪問其變量對象,依次類推,像這樣對不一樣的環境不一樣的變量對象進行訪問的路徑,咱們能夠稱之爲做用域鏈(scope chain)。
所以,所謂的標識符解析實際上就是沿着做用域鏈進行標識符的查找。
做用域鏈的訪問只能由前向後,由下向上,而不能反方向訪問,具體可見事例代碼:
var x = 1; function method(){ var y = 2; console.log(x); } method(); console.log(y); //Uncaught ReferenceError: y is not defined
也可見下圖:
在 JavaScript中有的語句能夠在當前的做用域鏈前端增長一個變量對象,從而延長做用域鏈。
對於 with
語句它會將指定的對象做爲變量對象添加至做用域鏈的前端。
function buildUrl(){ var search = '?debug=true'; with(location){ var url = href + search; #注意with並無做用域。 } return url; }
而對於 catch
語句而言則會建立一個新的對象,而後添加至當前做用域鏈的前端,在這個變量對象中保存的主要是錯誤對象 Error
的相關信息,例如 name,message等。
JavaScript (ES5) 中並不存在塊級做用域,ES5支持的做用域跟執行環境相同,主要有函數(局部)做用域、全局做用域。
聲明變量時,若是使用 var
關鍵字,則所聲明的變量添加至當前執行環境中的變量對象上,若是沒有使用 var
關鍵字,則默認添加至全局執行環境的的變量對象上。
不管是全局做用域仍是局部做用域,聲明變量時正確的操做都是使用
var
關鍵字去聲明。
JavaScript支持自動的垃圾回收機制,而不像C,C++那樣須要手動的跟蹤內存的使用狀況,而所謂的自動垃圾回收機制,其本質原理很是簡單,那就是每隔一段時間,週期性的檢查程序的執行狀況,將不在使用的標識符其所佔據的內存釋放,或者自動分配程序執行期間所須要的內存空間,這樣開發人員只需關注業務功能代碼,無需過多關心內存的使用狀況。
當咱們明白垃圾回收機制的大體原理時,那麼如何肯定一個標識符,其生命週期是否已經結束,就時垃圾自動回收的功能核心。
主流瀏覽器廠商,基本都時採用 「標記清除(mark and sweep)」的方式來標識那些變量能夠被回收,那些變量還具備引用關係不能被回收。
其大體思路時當一個變量進入執行環境時,會爲它添加一個標誌位,用於說明該變量進入了該環境,原則上永遠不能釋放進入環境中的變量,由於執行流程進入到相應的環境就有可能會用獲得。而當變量離開環境時,則再將其標誌置爲離開狀態。
這種標誌位的記錄方式有不少種,你能夠經過翻轉某一個位來記錄一個變量什麼時候進入環境,什麼時候離開環境,也可使用 map表的方式來分別記錄進入與離開時的狀態信息。
當JS的垃圾回收機制運行的時候,它會給存儲在內存中的全部變量都加上標記(可使用任何方式)而後它會去掉全局環境中的以及執行環境中具備引用關係的變量標記,而在此以後在被添加標記(能夠認爲是離開標記)都被視爲準備刪除的變量,緣由是這些變量已經不須要再被訪問了。
簡單的來歸納,那就時當變量進入環境時,其標記爲1,離開時置爲0,而後JS的垃圾回收機制每隔一段時間來掃描,將標記爲0的變量進行釋放。
用代碼來表示以下:
var status = 1 //進入環境 status = null //離開環境,或者爲Null的時候,立刻被垃圾回收機制回收。
「引用計數 (reference counting)」。引用計數實際上就是對值的一種計數標記,當咱們定義一個變量併爲它賦值一個引用類型時,這個引用類型的值其引用次數就默認爲1,當這個值還被其它的變量所引用,則引用次數加1。相反當引用了這個值的變量引用了別的值,則其引用次數減1。當這個值的引用次數爲0時,便說明這個值已經以及沒有被其它變量引用了,此時即可以將其所佔據的內存釋放出來。
引用計數的方式有一個很是嚴重的問題,那就是「循環引用」,當A的值有對B值的引用,而B值中也有對A值的引用時就會發生循環引用。
function problem(){ var ObjectA = new Object(); var ObjectB = new Object(); ObjectA.A = ObjectB; ObjectB.B = ObjectA; }
在這個事例中變量 ObjectA 與 ObjectB的值分別被引用了兩次,第一次是聲明變量並賦值的時候,第二次則是它們各自的屬性進行了交叉引用。因此此時這兩個對象的值引用次數就時 2,若是在標記清除的策略中並沒用什麼問題,可是在引用計數的方式下,ObjectA與ObjectB將在函數執行完畢後還會存在,由於它們的引用次數永遠不會爲0。假如這個函數被重複執行屢次,那麼就會致使更多的內存空間得不到回收。
採用「引用計數」方式的瀏覽器都很是古老了,主要是 Netspace Navigator 3.0 以及以前,因此不須要太擔憂,可是瞭解下仍是很是有必要的,這是由於在IE9以前,對於JavaScript中原生對象採用的是標記清除方式,可是對於非原生對象,例如BOM,DOM等垃圾回收機制依然仍是引用計數策略,因此若是須要兼容IE9如下版本的瀏覽器,在操做非原生對象時,就有可能會出現循環引用的問題。
事例代碼:
var element = document.getElementById('element'); var myObject = new Object(); myObject.element = element; element.hostObject = myObject;
對於這種狀況,咱們最好在書寫代碼的時候就要儘可能避免,若是難以免,則在程序執行完成之後也要記得手動釋放。
myObject.element = null; element.hostObject = null;
咱們知道垃圾回收機制是週期性的進行運行的,所以肯定垃圾收集的時間間隔是一個很是重要的問題。
在IE7以前,IE的回收機制都是根據內存的分配量進行的,具體一點就是256個變量,4096個對象(或數組)字面量和數組元素(slot)或者64KB的字符串,達到上述的任何一個臨界值,垃圾收集器就會運行。這種實現方式的問題在於若是一個腳本具備很是多的變量,那麼腳本極可能會在其生命週期中一直保有那麼多的變量。這樣一來,垃圾收集器就不得不頻繁的執行,從而影響了正常的程序執行。
到了IE7後,微軟重寫了IE瀏覽器的垃圾回收機制,觸發垃圾收集的變量分配、字面量和(或)數組元素的臨界值被調整爲動態修正,其各項臨界值在初始時與IE6及以前的版本相同,若是垃圾收集例程回收的內存分配量低於15%,則變量、字面量和(或)數組元素的臨界值就會加倍,若是例程回收了85%的內存分配量,則再將各臨界值重置爲默認值。
在平常編碼時還有另外一種方式能夠很好的提高執行的性能,那就是爲再也不使用到的變量賦值 null
來進行手動的釋放。通常來講這種方式經常使用於全局變量,由於局部變量會由垃圾回收機制自行清理。
function doSomething() { var obj = new Object(); return obj.name = 'csutom'; } var global_var = doSomething(); //... global_var = null; //當不用的時候最好記得手動釋放