淺談js之閉包

1.什麼是閉包???javascript

"官方"的解釋是指一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分;css

紅皮書是這樣說的,閉包是指有權訪問另外一個函數做用域中變量的函數;常見的建立閉包的方式就是在一個函數中再建立一個函數;html

閉包是一種特殊的對象。它由兩部分構成:函數,以及建立該函數的環境。環境由閉包建立時在做用域中的任何局部變量組成;前端

光看定義是雲裏霧裏,可是到了真正的代碼了又是什麼樣的形式呢?經典的閉包例子:java

function fn() {
    var name = 4;
    return function () {
        var n = 0;
        alert(++n);
        alert(++name);
    }
}
var fun = fn();
fun();//n =>1,name=>5;
fun();// n =>1,name=>6;

 

這裏就有閉包產生了,fun就是閉包;這個閉包由fn中的匿名函數和name變量構成。,可是呢,它又是一種特殊的函數,它是一種能可以讀取其餘函數內部變量的特殊函數;fn中的匿名函數的父函數在執行完了以後,按正常來講,它應該被銷燬,裏面的變量也被銷燬,,可是裏面的變量如今沒有被銷燬,而是被這個匿名函數引用着;(說實在的它不該該被銷燬,由於這個匿名函數尚未執行,還要用上一級的函數的中的變量,你給我銷燬了,我可怎麼辦,那不是讓我報錯呀,可是呢,我給你用,不銷燬,這又不符合規矩,按規矩是這樣的:當一個函數執行完以後,是要被當即銷燬的,執行環境銷燬,裏面的變量銷燬,你如今不讓我銷燬,那不亂套了,那怎麼辦呢,因而乎,一羣磚家,就說趕這種方式叫閉包吧)意思是說我跟大家不同;由於是特殊函數,代碼的最後一句fun()執行完以後,name變量仍是沒有釋放,可是每次執行fun,裏面的n變量是都是新建立的,執行完以後又釋放掉;要是你明白了就不須要看括號裏的內容了(這裏我就形象的說爲何說name變量會一直在內存中?在剛開始的時候,父函數fn在剛要執行完了,開始銷燬時,匿名子函數就說了,我要用你的name變量,你先別銷燬了,父函數說好吧,因而乎,父函數執行完以後(歸天了),就沒有銷燬name,在當你調用fun,執行匿名子函數,fun()調用完了,你把本身家的n變量銷燬了,fun就說了name又不是個人東西,我就是用了一下,憑什麼我給銷燬,我不給銷燬,可是這時父函數已經去世了(執行完了),因而就產生了內存消耗,除非你手動銷燬,垃圾收回機制不會自動收回;這又牽扯到內存泄漏,性能問題了,後面說。)閉包

做用域鏈:ide

講到這裏,若是要想整整的明白還有知道做用域鏈,和垃圾收回機制;函數

我就說說上面代碼執行時的做用域鏈:性能

我就說說這張圖是什麼意思,這種圖是執行var fun = fu();fun();這兩句代碼時所發生的狀況;ui

其實匿名函數在fu()被返回時,它的做用域鏈就被初始化爲包含全局變量對象和fu函數的活動對象;也就是說當fu函數執行完返回後,它的執行環境會被銷燬,可是其活動對象不會被銷燬,仍然在內存中,由於匿名函數的做用域鏈中引用了這個活動對象。只有到匿名函數被手動銷燬時才銷燬;其實在fu執行完後,紅字顯示的部分就消失了,就活動變量沒有消失;

再說一點關於做用域鏈的問題:

1。做用域鏈中的變量對象(函數中叫活動對象)保存的是變量和函數;

2.做用域鏈的做用就是爲了保證對執行環境有權訪問的全部變量和函數的有序訪問。

3.查找一個變量是從做用域鏈的最前端,逐漸向上找,只要找到就再也不向上找了,不論上面是否還有這個值;

4.就以上面的fu函數爲例吧,在聲明fu函數的時候就開始預先建立一個包含全局變量對象的做用域鏈了(若是嵌套多了,其實就是在函數聲明的地方建立父函數及其之上的做用域鏈),這個做用域鏈將被保存在剛建立函數的內部[[Scope]]的屬性中;當調用fu函數時,會爲函數建立一個執行環境,而後經過複製函數的[[Scope]]中的做用域鏈構建起執行環境的做用域鏈;以後還要建立一個本函數的活動對象,並把這個活動對象推入執行環境做用域鏈的前端。

5.做用域鏈本質上就是一個指向變量對象的指針列表。

垃圾回收機制:

再說說垃圾回收機制:

1.若是一個對象再也不有引用了,這個對象就會被GC收回;

2,若是兩個對象互相引用,但不被第三個引用,這兩個互相引用的對象也會收回的。

而閉包再也不這個範疇以內。

閉包的特性:

1.引用的變量不被垃圾回收機制收回

2.函數內部能夠引用外部的變量;

3.函數裏面嵌套函數

閉包的用處(好處):

1.私有變量和方法

var a=(function() {
    var privateNum = 1;
    function privateFun(val) {
        alert(val);
    }
    return {
        publicFun: function() {
            privateFun(2);
        },
        publicNum:function() {
            return privateNum;
        }
    }

    })();
    a.publicNum();//1
    a.publicFun();//2

若是你用a.privateNum,a.privateFun();這是會報錯的。

2.實現一些變量的累加

 

function a() {
    var n=0;
    return function () {
        n++;
        alert(n);
    }
}
var b = a();
b();//1
b();//2
b();//3

這裏只是要使用累加,就這樣幹,具體還要具體分析,原理是這樣了

因閉包產生的問題

初學者常見的,循環閉包

大部分咱們所寫的 Web JavaScript 代碼都是事件驅動的 — 定義某種行爲,而後將其添加到用戶觸發的事件之上(好比點擊或者按鍵)。咱們的代碼一般添加爲回調:響應事件而執行的函數。

<!DOCTYPE HTML>
 <html>
  <head>
   <meta charset="utf-8"/>
   <title>閉包循環問題</title>
   <style type="text/css">
     p {background:red;}
   </style>
 </head> 
 <body> 
  <p class="p">我是1號</p>
  <p class="p">我是2號</p>
  <p class="p">我是3號</p>
  <p class="p">我是4號</p>
  <p class="p">我是五號</p>
<script type="text/javascript">
var page = document.getElementsByTagName("p");
for(var i=0; i<page.length; i++) {
  page[i].onclick = function () {
    alert("我是"+i+"號");
  }
} 
</script> 
</body> 
</html>
閉包循環問題

你無論點擊哪個,都alert」我是5號「;
緣由就是你循環了五次產生了五個閉包,而這5個閉包共享一個變量i,說的明白一點就是,在for循環結束時,只是把這五個匿名函數註冊給click事件,當時在循環的時候並無執行,當循環結束了,此時i的值是5;以後你去點擊p標籤,你點擊哪個就執行哪個對應的匿名函數(這個時候才執行),這時候匿名中發現一個i,匿名中沒有定義i,因而沿着做用域鏈找,找到了,可是這時候循環早就結束了,i等於5,因而彈出」我是5號「來;點擊其餘的同理;

怎麼解決呢:

一種方法是再建立一個閉包,把js代碼改成這樣就好了

var page = document.getElementsByTagName("p");
for(var i=0; i<page.length; i++) {
  !function(num) {
  page[i].onclick = function () {
    alert("我是"+num+"號");
  }
  }(i)
}

我只說一點,此次五個閉包不共享num,而是建立五個num變量

還有一種解決方式:

var page = document.getElementsByTagName("p");
for(var i=0; i<page.length; i++) {
  page[i].num = i//先把每一個變量值存起來
  page[i].onclick = function () {
    alert("我是"+this.num+"號");
  }
} 

閉包中的this對象

var num = 1;
var obj = {
  num:2,
  getNum:function() {
    return function () {
      return this.num;
    }
}
}
alert(obj.getNum()());//num -> 1

爲何不彈出2呢,這裏是說明閉包中你須要注意如今的this的指向那一個對象,其實記住一句話就永遠不會用錯this的指向問題,this永遠指向調用它的做用域;

若是這樣寫你就可能理解了

var num = 1;
var obj = {
  num:2,
  getNum:function() {
    return function () {
      return this.num;
    }
}
}
var a = obj.getNum();
alert(window.a());//1

實際上是window對象調用的,這就是說閉包中的this讓你看不清this的指向;

要是讓它alert 2你要這樣:

var num = 1;
var obj = {
  num:2,
  getNum:function() {
    var _this = this;//在這裏保存this
    return function () {
      return _this.num;
    }
}
}
var a = obj.getNum();
alert(window.a());

性能考量

若是不是由於某些特殊任務而須要閉包,在沒有必要的狀況下,在其它函數中建立函數是不明智的,由於閉包對腳本性能具備負面影響,包括處理速度和內存消耗。

例如,在建立新的對象或者類時,方法一般應該關聯於對象的原型,而不是定義到對象的構造器中。緣由是這將致使每次構造器被調用,方法都會被從新賦值一次(也就是說,爲每個對象的建立)。

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

應該是這樣

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

示例中,繼承的原型能夠爲全部對象共享,且沒必要在每一次建立對象時定義方法

 歡迎評論指正;

參考:

紅皮書

Mozilla:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Closures

阮一峯:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

相關文章
相關標籤/搜索