詳解JS 變量、作用域及內(nèi)存

2016-10-26    藍(lán)藍(lán)設(shè)計(jì)的小編

如果您想訂閱本博客內(nèi)容,每天自動(dòng)發(fā)到您的郵箱中, 請(qǐng)點(diǎn)這里

 

基本類(lèi)型值有:undefined,NUll,Boolean,Number和String,這些類(lèi)型分別在內(nèi)存中占有固定的大小空間,他們的值保存在??臻g,我們通過(guò)按值來(lái)訪問(wèn)的。

 

1)值類(lèi)型:數(shù)值、布爾值、null、undefined
2)引用類(lèi)型:對(duì)象、數(shù)組、函數(shù)。
 
如果賦值的是引用類(lèi)型的值,則必須在堆內(nèi)存中為這個(gè)值分配空間。由于這種值的大小不固定(對(duì)象有很多屬性和方法),因此不能把他們保存到棧內(nèi)存中。但內(nèi)存地址大小是固定的,因此可以將內(nèi)存地址保存在棧內(nèi)存中。
 
<script type="text/javascript”>
var box = new Object();  //創(chuàng)建一個(gè)引用類(lèi)型
var box = "trigkit4";   //基本類(lèi)型值是字符串
box.age = 21;    //基本類(lèi)型值添加屬性很怪異,因?yàn)橹挥袑?duì)象才可以添加屬性。
alert(box.age);  //不是引用類(lèi)型,無(wú)法輸出;
</script>
 
簡(jiǎn)而言之,堆內(nèi)存存放引用值,棧內(nèi)存存放固定類(lèi)型值?!耙谩笔且粋€(gè)指向?qū)ο髮?shí)際位置的指針。

在這里需注意的是,引用指向的是具體的對(duì)象,而不是另一個(gè)引用。

6aa5ac5dede866494318b109fae59bc4.jpeg

 

 

 這里的對(duì)象可以是字符串對(duì)象,數(shù)字對(duì)象,數(shù)組對(duì)象等

<script type="text/javascript">
    var man = new Object();//man指向了棧內(nèi)存的空間地址
    man.name = "Jack";
    var man2 = man;//man2獲得了man的指向地址
 
    alert(man2.name);//兩個(gè)都彈出Jack
    alert(man.name);
</script>

 

復(fù)制變量值

再看下面這個(gè)例子:

 

<script type="text/javascript">
    var man = new Object();//man指向了棧內(nèi)存的空間地址
    man.name = "Jack";
    var man2 = man;//man2獲得了man的指向地址
 
    man2.name = "ming";//因?yàn)樗麄兌贾赶蛲粋€(gè)object,同一個(gè)name,不管修改誰(shuí),大家都修改了
    alert(man2.name);//兩個(gè)都彈出ming
    alert(man.name);
</script>
 

由以上可以得出:在變量復(fù)制方面,基本類(lèi)型和引用類(lèi)型也有所不同,基本類(lèi)型復(fù)制的是值本身,而引用類(lèi)型復(fù)制的是地址。

傳遞參數(shù)

ECMAScript中,所有函數(shù)的參數(shù)都是按值傳遞的,

 

<script type="text/javascript">
     function box(num){      //按值傳遞
         num+=10;
         return num;
     }
 
     var num = 10;
     var result = box(num);
     alert(result);  //如果是按引用傳遞,那么函數(shù)里的num會(huì)成為類(lèi)似全局變量,把外面的number替換掉
     alert(num);    //也就是說(shuō),最后應(yīng)該輸出20(這里輸出10)
</script>
 

js沒(méi)有按引用傳遞的,如果存在引用傳遞的話(huà),那么函數(shù)內(nèi)的變量將是全局變量,在外部也可以訪問(wèn)。但這明顯是不可能的。

執(zhí)行環(huán)境及作用域

執(zhí)行環(huán)境是javascript中最為重要的概念之一,執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問(wèn)其他數(shù)據(jù)。

全局執(zhí)行環(huán)境是最外圍的執(zhí)行環(huán)境,在web瀏覽器中,全局執(zhí)行環(huán)境是window對(duì)象,因此,所有的全局變量的函數(shù)都是作為window的屬性和方法創(chuàng)建的。

 

<script type="text/javascript">
      var name = "Jack";           //定義全局變量
      function setName(){
          return "trigkit4";
      }
 
      alert(window.name);        //全局變量,最外圍,屬于window屬性
      alert(window.setName());  //全局函數(shù),最外圍,屬于window方法
</script>

 

當(dāng)執(zhí)行環(huán)境內(nèi)的代碼執(zhí)行完畢后,該環(huán)境被銷(xiāo)毀,保存其中的變量和函數(shù)也隨之銷(xiāo)毀,如果是全局環(huán)境,需所有程序執(zhí)行完畢或網(wǎng)頁(yè)完畢后才會(huì)銷(xiāo)毀。

去掉var的局部變量

<script type="text/javascript">
      var name = "Jack";
      function setName(){
          name = "trigkit4";   //去掉var變成了全局變量
      }
 
      setName();
      alert(name);//彈出trigkit4
</script>
 

通過(guò)傳參,也是局部變量

<script type="text/javascript">
      var name = "Jack";
      function setName(name){    //通過(guò)傳參,也是局部變量
          alert(name);
      }
 
      setName("?trigkit4");//彈出trigkit4
      alert(name);//彈出Jack
</script>
 
函數(shù)體內(nèi)還包含函數(shù),只有這個(gè)函數(shù)才可以訪問(wèn)內(nèi)一層的函數(shù)
 
<script type="text/javascript">
     var name = "Jack";
      function setName(){
          function setYear(){    //setYear()方法的作用域在setName()內(nèi)
              return 21;
          }
      }
      alert(setYear());//無(wú)法訪問(wèn),出錯(cuò)
</script>
 
可以通過(guò)如下方法進(jìn)行訪問(wèn):
 
<script type="text/javascript">
     var name = "Jack";
      function setName(){
          function setYear(){    //setYear()方法的作用域在setName()內(nèi)
              return 21;
          }
          return setYear();
      }
      alert(setName()); //彈出21
</script>
 
再一個(gè)作用域例子:
 
<script type="text/javascript">
     var name = "Jack";
      function setName(){
          function setYear(){    //setYear()方法的作用域在setName()內(nèi)
              var b = "hi";     //變量b的作用域在setYear()內(nèi)
              return 21;
          }
          alert(b);//無(wú)法訪問(wèn)
      }
</script>
 

當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行的時(shí)候,就會(huì)形成一種叫做作用域鏈的東西,它的用途是保證對(duì)執(zhí)行環(huán)境中有訪問(wèn)權(quán)限的變量和函數(shù)進(jìn)行有序訪問(wèn)(指按照規(guī)則層次來(lái)訪問(wèn)),作用域鏈的前端,就是執(zhí)行環(huán)境的變量對(duì)象。

作用域

變量沒(méi)有在函數(shù)內(nèi)聲明或者聲明的時(shí)候沒(méi)有帶var就是全局變量,擁有全局作用域,window對(duì)象的所有屬性擁有全局作用域;在代碼任何地方都可以訪問(wèn),函數(shù)內(nèi)部聲明并且以var修飾的變量就是局部變量,只能在函數(shù)體內(nèi)使用,函數(shù)的參數(shù)雖然沒(méi)有使用var但仍然是局部變量。

沒(méi)有塊級(jí)作用域

 

// if語(yǔ)句:
 
 
<script type="text/javascript">
if(true){                        //if語(yǔ)句的花括號(hào)沒(méi)有作用域的功能。
 
var box = "trigkit4";
}
alert(box);//彈出 trigkit4
</script>
 

for循環(huán)語(yǔ)句也是如此。

變量的查詢(xún)

在變量的查詢(xún)中,訪問(wèn)局部變量要比全局變量來(lái)得快,因此不需要向上搜索作用域鏈。
如下例子:

<script type="text/javascript">
     var name = "Jack";
      function setName(){
           var name = "trigkit4";
           return name;  //從底層向上搜索變量
    }
    alert(setName());      
</script>
 

每個(gè)環(huán)境都可以向上搜索作用域鏈,以查詢(xún)變量和函數(shù)名;但任何環(huán)境都不能通過(guò)向下搜索作用域鏈而進(jìn)入另一個(gè)執(zhí)行環(huán)境。在這里,如果去掉var name = "trigkit4",那么將彈出“Jack”

內(nèi)存問(wèn)題

javascript具有自動(dòng)垃圾回收機(jī)制,一旦數(shù)據(jù)不再使用,可以將其設(shè)為”null”來(lái)釋放引用

循環(huán)引用

一個(gè)很簡(jiǎn)單的例子:一個(gè)DOM對(duì)象被一個(gè)Javascript對(duì)象引用,與此同時(shí)又引用同一個(gè)或其它的Javascript對(duì)象,這個(gè)DOM對(duì)象可能會(huì)引發(fā)內(nèi)存泄露。這個(gè)DOM對(duì)象的引用將不會(huì)在腳本停止的時(shí)候被垃圾回收器回收。要想破壞循環(huán)引用,引用DOM元素的對(duì)象或DOM對(duì)象的引用需要被賦值為null

閉包

在閉包中引入閉包外部的變量時(shí),當(dāng)閉包結(jié)束時(shí)此對(duì)象無(wú)法被垃圾回收(GC)。

 

var a = function() {
  var largeStr = new Array(1000000).join('x');
  return function() {
    return largeStr;
  }
}()

DOM泄露

當(dāng)原有的COM被移除時(shí),子結(jié)點(diǎn)引用沒(méi)有被移除則無(wú)法回收。

 

var select = document.querySelector;
var treeRef = select('#tree');
 
//在COM樹(shù)中l(wèi)eafRef是treeFre的一個(gè)子結(jié)點(diǎn)
var leafRef = select('#leaf');
var body = select('body');
 
body.removeChild(treeRef);
 
//#tree不能被回收入,因?yàn)閠reeRef還在
//解決方法:
treeRef = null;
 
//tree還不能被回收,因?yàn)槿~子結(jié)果leafRef還在
leafRef = null;
 
//現(xiàn)在#tree可以被釋放了。
 

Timers計(jì)(定)時(shí)器泄露

定時(shí)器也是常見(jiàn)產(chǎn)生內(nèi)存泄露的地方:

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

 

調(diào)試內(nèi)存

Chrome自帶的內(nèi)存調(diào)試工具可以很方便地查看內(nèi)存使用情況和內(nèi)存泄露:
在 Timeline -> Memory 點(diǎn)擊record即可:

 

藍(lán)藍(lán)設(shè)計(jì)www.bouu.cn )是一家專(zhuān)注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì)  圖標(biāo)定制 、 用戶(hù)體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù) 

 

分享本文至:

日歷

鏈接

個(gè)人資料

存檔