JavaScript變量、作用域及內存詳解
基本類型值有:undefined,NUll,Boolean,Number和String
,這些類型分別在內存中占有固定的大小空間,他們的值保存在??臻g,我們通過按值來訪問的。
(1)值類型:數值、布爾值、null、undefined。
(2)引用類型:對象、數組、函數。
如果賦值的是引用類型的值,則必須在堆內存中為這個值分配空間。由于這種值的大小不固定(對象有很多屬性和方法),因此不能把他們保存到棧內存中。但內存地址大小是固定的,因此可以將內存地址保存在棧內存中。
<script type="text/javascript”> var box = new Object(); //創建一個引用類型 var box = "trigkit4"; //基本類型值是字符串 box.age = 21; //基本類型值添加屬性很怪異,因為只有對象才可以添加屬性。 alert(box.age); //不是引用類型,無法輸出; </script>
簡而言之,堆內存存放引用值,棧內存存放固定類型值。“引用”是一個指向對象實際位置的指針。
在這里需注意的是,引用指向的是具體的對象,而不是另一個引用。
這里的對象可以是字符串對象,數字對象,數組對象等
- <script type="text/javascript">
- var man = new Object();//man指向了棧內存的空間地址
- man.name = "Jack";
- var man2 = man;//man2獲得了man的指向地址
- alert(man2.name);//兩個都彈出Jack
- alert(man.name);
- </script>
復制變量值
再看下面這個例子:
- <script type="text/javascript">
- var man = new Object();//man指向了棧內存的空間地址
- man.name = "Jack";
- var man2 = man;//man2獲得了man的指向地址
- man2.name = "ming";//因為他們都指向同一個object,同一個name,不管修改誰,大家都修改了
- alert(man2.name);//兩個都彈出ming
- alert(man.name);
- </script>
由以上可以得出:在變量復制方面,基本類型和引用類型也有所不同,基本類型復制的是值本身,而引用類型復制的是地址。
傳遞參數
ECMAScript中,所有函數的參數都是按值傳遞的,
- <script type="text/javascript">
- function box(num){ //按值傳遞
- num+=10;
- return num;
- }
- var num = 10;
- var result = box(num);
- alert(result); //如果是按引用傳遞,那么函數里的num會成為類似全局變量,把外面的number替換掉
- alert(num); //也就是說,***應該輸出20(這里輸出10)
- </script>
js
沒有按引用傳遞的,如果存在引用傳遞的話,那么函數內的變量將是全局變量,在外部也可以訪問。但這明顯是不可能的。
執行環境及作用域
執行環境是javascript
中最為重要的概念之一,執行環境定義了變量或函數有權訪問其他數據。
全局執行環境是最外圍的執行環境,在web瀏覽器中,全局執行環境是window
對象,因此,所有的全局變量的函數都是作為window
的屬性和方法創建的。
- <script type="text/javascript">
- var name = "Jack"; //定義全局變量
- function setName(){
- return "trigkit4";
- }
- alert(window.name); //全局變量,最外圍,屬于window屬性
- alert(window.setName()); //全局函數,最外圍,屬于window方法
- </script>
當執行環境內的代碼執行完畢后,該環境被銷毀,保存其中的變量和函數也隨之銷毀,如果是全局環境,需所有程序執行完畢或網頁完畢后才會銷毀。
去掉var的局部變量
- <script type="text/javascript">
- var name = "Jack";
- function setName(){
- name = "trigkit4"; //去掉var變成了全局變量
- }
- setName();
- alert(name);//彈出trigkit4
- </script>
通過傳參,也是局部變量
- <script type="text/javascript">
- var name = "Jack";
- function setName(name){ //通過傳參,也是局部變量
- alert(name);
- }
- setName("trigkit4");//彈出trigkit4
- alert(name);//彈出Jack
- </script>
函數體內還包含函數,只有這個函數才可以訪問內一層的函數
- <script type="text/javascript">
- var name = "Jack";
- function setName(){
- function setYear(){ //setYear()方法的作用域在setName()內
- return 21;
- }
- }
- alert(setYear());//無法訪問,出錯
- </script>
可以通過如下方法進行訪問:
- <script type="text/javascript">
- var name = "Jack";
- function setName(){
- function setYear(){ //setYear()方法的作用域在setName()內
- return 21;
- }
- return setYear();
- }
- alert(setName()); //彈出21
- </script>
再一個作用域例子:
- <script type="text/javascript">
- var name = "Jack";
- function setName(){
- function setYear(){ //setYear()方法的作用域在setName()內
- var b = "hi"; //變量b的作用域在setYear()內
- return 21;
- }
- alert(b);//無法訪問
- }
- </script>
當代碼在一個環境中執行的時候,就會形成一種叫做作用域鏈
的東西,它的用途是保證對執行環境中有訪問權限的變量和函數進行有序訪問(指按照規則層次來訪問),作用域鏈的前端,就是執行環境的變量對象。
作用域
變量沒有在函數內聲明或者聲明的時候沒有帶var
就是全局變量,擁有全局作用域,window
對象的所有屬性擁有全局作用域;在代碼任何地方都可以訪問,函數內部聲明并且以var
修飾的變量就是局部變量,只能在函數體內使用,函數的參數雖然沒有使用var
但仍然是局部變量。
沒有塊級作用域
- // if語句:
- <script type="text/javascript">
- if(true){ //if語句的花括號沒有作用域的功能。
- var box = "trigkit4";
- }
- alert(box);//彈出 trigkit4
- </script>
for循環語句也是如此。
變量的查詢
在變量的查詢中,訪問局部變量要比全局變量來得快,因此不需要向上搜索作用域鏈。
如下例子:
- <script type="text/javascript">
- var name = "Jack";
- function setName(){
- var name = "trigkit4";
- return name; //從底層向上搜索變量
- }
- alert(setName());
- </script>
每個環境都可以向上搜索作用域鏈,以查詢變量和函數名;但任何環境都不能通過向下搜索作用域鏈而進入另一個執行環境。在這里,如果去掉var name = "trigkit4"
,那么將彈出“Jack”
內存問題
javascript
具有自動垃圾回收機制,一旦數據不再使用,可以將其設為”null”來釋放引用
循環引用
一個很簡單的例子:一個DOM對象被一個Javascript
對象引用,與此同時又引用同一個或其它的Javascript
對象,這個DOM
對象可能會引發內存泄露。這個DOM
對象的引用將不會在腳本停止的時候被垃圾回收器回收。要想破壞循環引用,引用DOM
元素的對象或DOM
對象的引用需要被賦值為null
。
在閉包中引入閉包外部的變量時,當閉包結束時此對象無法被垃圾回收(GC)。
var a = function() {
var largeStr = new Array(1000000).join('x');
return function() {
return largeStr;
}
}();
DOM泄露
當原有的COM被移除時,子結點引用沒有被移除則無法回收。
var select = document.querySelector;
var treeRef = select('#tree');
//在COM樹中leafRef是treeFre的一個子結點
var leafRef = select('#leaf');
var body = select('body');
body.removeChild(treeRef);
//#tree不能被回收入,因為treeRef還在
//解決方法:
treeRef = null;
//tree還不能被回收,因為葉子結果leafRef還在
leafRef = null;
//現在#tree可以被釋放了。
Timers計(定)時器泄露
定時器也是常見產生內存泄露的地方:
- for (var i = 0; i < 90000; i++) {
- var buggyObject = {
- callAgain: function() {
- var ref = this;
- var val = setTimeout(function() {
- ref.callAgain();
- }, 90000);
- }
- }
- buggyObject.callAgain();
- //雖然你想回收但是timer還在
- buggyObject = null;
- }
調試內存
Chrome
自帶的內存調試工具可以很方便地查看內存使用情況和內存泄露,在 Timeline -> Memory 點擊record即可。