詳解js變量、做用域及內存

贊助我以寫出更好的文章,give me a cup of coffee?javascript


基本類型值有:undefined,NUll,Boolean,Number和String,這些類型分別在內存中佔有固定的大小空間,他們的值保存在棧空間,咱們經過按值來訪問的。前端

(1)值類型:數值、布爾值、null、undefined。
(2)引用類型:對象、數組、函數。

若是賦值的是引用類型的值,則必須在堆內存中爲這個值分配空間。因爲這種值的大小不固定(對象有不少屬性和方法),所以不能把他們保存到棧內存中。但內存地址大小是固定的,所以能夠將內存地址保存在棧內存中。java

<script type="text/javascript」>
var box = new Object();  //建立一個引用類型
var box = "trigkit4";   //基本類型值是字符串
box.age = 21;    //基本類型值添加屬性很怪異,由於只有對象才能夠添加屬性。
alert(box.age);  //不是引用類型,沒法輸出;
</script>

簡而言之,堆內存存放引用值,棧內存存放固定類型值。「引用」是一個指向對象實際位置的指針。git

在這裏需注意的是,引用指向的是具體的對象,而不是另外一個引用。github

bVcTZz

這裏的對象能夠是字符串對象,數字對象,數組對象等web

<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>

由以上能夠得出:在變量複製方面,基本類型和引用類型也有所不一樣,基本類型複製的是值自己,而引用類型複製的是地址。segmentfault

傳遞參數

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便可:

相關文章:

詳解js閉包

咱們能夠利用閉包來突破做用域鏈,詳解js閉包

相關文章
相關標籤/搜索