1. 程式人生 > >變數、作用域鏈和記憶體問題

變數、作用域鏈和記憶體問題

基本型別和引用型別

如果從一個變數向另一個變數複製基本型別的值,會在變數物件上建立一個新值,然後把該值複製到為新變數分配的位置上。
當從一個變數向另一個變數複製引用型別的值時,同樣也會將儲存在變數物件中的值複製一份放到為新變數分配的空間中。不同的是,這個值的副本實際上是一個指標,而這個指標指向儲存在堆中的一個物件。複製操作結束後,兩個變數實際上將引用同一個物件。

傳遞引數(只能按值傳遞)

ECMAScript中所有函式的引數都是按值傳遞的,也就是說,把函式外部的值複製給函式內部的引數,就和把值從一個變數複製到另一個變數一樣。基本型別值的傳遞如同基本型別變數的複製一樣,而引用型別值的傳遞,則如同引用型別變數的複製一樣。
<script type="text/javascript">
function setName(obj){
	obj.name = "lisi";
	obj = new Object();
	obj.name = "wangwu";
}
var person = new Object();
setName(person);//物件是按值傳遞的
console.log(person.name);//lisi
/*
在把person傳遞給setName()後,其name屬性就被設定為"lisi"
當在函式內部重寫obj時,這個變數引用的就是一個區域性物件。這個區域性物件會在函式執行完畢後立即銷燬
 */
</script>

執行環境及作用域

執行環境定義了變數或者函式有權訪問的其他資料,決定了它們各自的行為。每個執行環境都有一個與之關聯的變數物件,環境中定義的所有變數和函式都儲存在這個物件中。
在Web瀏覽器中,全域性執行環境被認為是window物件,因此所有全域性變數和函式都是作為window物件的屬性和方法建立的。
作用域鏈的作用是:保證對執行環境有權訪問的所有變數和函式的有序訪問
作用域鏈的變數只能向上訪問,變數訪問到window物件即被終止,作用域鏈向下訪問變數是不被允許的。
函式宣告最先進記憶體,其他程式碼依次執行
alert(abc);//輸出函式宣告
        function abc(){
        console.log("I am student.");
    }


var name="liujie";//全域性變數
console.log('全域性-name:'+name);//當前環境 liujie
 function f1()//內部環境 
{ 
console.log('f1-name:'+name); 
function f2()//內部深層環境 f2是f1的區域性變數 
{ 
console.log('f2-name:'+name); 
function f3()//內部深層環境 
{ console.log('f3-name:'+name);
 } 
f3();//liujie
 }
 f2();//liujie }
 f1();//liujie

內部環境可以通過作用域鏈訪問所有的外部環境,但是外部環境不能訪問內部環境中的任何變數和函式。

var week='sunday';
   function abc(){
    var month='1yuefen';
    console.log(week);//sunday
   }
   console.log(month);//ReferenceError: month is not defined
   abc();

//自己有就訪問自己的,自己沒有就訪問全域性的
    var week='sunday';
    function f1(){
        console.log('f1星期:'+week);//Friday
    }
    function f2(){
        var week='monday';
        console.log('f2星期:'+week);//monday
        f1();
    }
    week="Friday";
    f2();
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
    <script type="text/javascript">
        (function(){//這裡是塊級作用域,通常稱為私有作用域
            a=5;
            //這裡是undefined,是因為下面的var a的變數宣告提升造成的,將a的宣告提升到作用域的最前方
            console.log(window.a);// undefined
            var a=1;
            console.log(a);//1
        })();
    </script>
    </body>
</html>

垃圾收集

JavaScript具有自動垃圾收集機制,也就是說,執行環境會負責管理程式碼執行過程中使用的記憶體。在編寫程式時,開發人員不用再關心記憶體使用問題,所需記憶體的分配以及無用記憶體的會受完全實現了自動管理。
原理:找出那些不再繼續使用的變數,然後釋放其佔用的記憶體。為此,垃圾收集器會按照固定的時間間隔週期性地執行這一操作。

最常用的垃圾收集機制:標記清除

垃圾收集器在執行的時候回給儲存在記憶體中的所有變數都加上標記。然後,它會去掉環境中的變數以及被環境中的變數引用的變數的標記。而在此之後再被加上標記的變數將被視為準備刪除的變數,原因是環境中的變數已經無法訪問到這些變量了。最後,垃圾收集器完成記憶體清除工作,消除那些帶標記的值並回收它們所佔用的記憶體空間。
<span style="font-weight: normal;"><script type="text/javascript">
	/*
	迴圈引用:指的是物件A中包含一個指向物件B的指標,而物件B中也包含一個指向物件A的引用
	這個例子中,objectA和objectB通過各自的屬性相互引用;這兩個物件的引用次數都是2.
	在採用標記清除策略的實現中,由於函式執行之後,這兩個物件都離開了作用域,因此這種相互引用不是問題
	但在引用計數策略的實現中,當函式執行完畢後,objectA和objectB還會繼續存在,因為他們的引用次數永遠不會是0。假如這個函式被重複多次呼叫,就會導致大量的記憶體得不到回收。
	*/
function fn(){
	var objectA = new Object();
	var objectB = new Object();
}
objectA.data = objectB;
objectB.data = objectA;
	</script></span>
<span style="font-weight: normal;"><script type="text/javascript">
	var ele = document.getElementById("some_ele");
	var myObject = new Object();
	myObject.element = ele;
	ele.someObject = myObject;
	/*
	這裡DOM元素與原生js物件之間建立了迴圈引用
	由於存在這個迴圈引用,即使將例子中的DOM從頁面中移除,它也永遠不會被回收
	為了避免類似這樣的迴圈引用問題,最好是不在使用它們的時候手工斷開原生JavaScript物件與DOM元素之間的連線。
	myObject.element = null;
	ele.someObject = null;
	將變數設定為null意味著切斷變數與它以前引用的值之間的連線。當垃圾收集器下次執行時,就會刪除這些值並回收它們佔用的記憶體。
	 */
</script></span>

管理記憶體 確保佔用最少的記憶體可以讓頁面獲得更好的效能。而優化記憶體佔用的最佳方式,就是為執行中的程式碼只保留必要的資料。一旦資料不再有用,最好通過將其值設定為null來釋放其引用。
我們一般只需要將大多數全域性變數和全域性物件的屬性進行解除引用(在我們不需要的時候),區域性變數會在它們離開執行環境時自動被解除引用。