JS中的閉包

閉包

閉包(closure)是javascript的一大難點,也是它的特點。不少高級應用都要依靠閉包來實現。javascript

變量做用域

要理解閉包,首先要理解javascript的特殊的變量做用域。java

變量的做用域無非就兩種:全局變量和局部變量。數組

javascript語言的特別之處就在於: 函數內部能夠直接讀取全局變量,可是在函數外部沒法讀取函數內部的局部變量。安全

注意點:在函數內部聲明變量的時候,必定要使用var命令。 若是不用的話,你實際上聲明的是一個全局變量!閉包

如何從外部讀取函數內部的局部變量

出於種種緣由,咱們有時候須要獲取到函數內部的局部變量。可是,上面已經說過了,正常狀況下,這是辦不到的!只有經過變通的方法才能實現。dom

那就是在函數內部,再定義一個函數。模塊化

function f1(){
    var n=999;
    function f2(){
        alert(n); // 999
    }
}
複製代碼

在上面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的全部局部變量,對f2都是可見的。可是反過來就不行,f2內部的局部變量,對f1就是不可見的。函數

這就是Javascript語言特有的 "鏈式做用域"結構(chain scope), 子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。性能

既然f2能夠讀取f1中的局部變量,那麼只要把f2做爲返回值,咱們不就能夠在f1外部讀取它的內部變量了嗎!ui

閉包的概念

上面代碼中的f2函數,就是閉包。

各類專業文獻的閉包定義都很是抽象,個人理解是: 閉包就是可以讀取其餘函數內部變量的函數。

因爲在javascript中,只有函數內部的子函數才能讀取局部變量,因此說,閉包能夠簡單理解成「定義在一個函數內部的函數「。

因此,在本質上,閉包是將函數內部和函數外部鏈接起來的橋樑。

閉包的用途

一、函數做爲返回值,二、函數做爲參數傳遞。

  • 函數做爲返回值
function fn() {
  var max = 10;
  return function bar(x) {
    if(x > max){
      console.log(x)
    }
  }
}
複製代碼

如上代碼,bar函數做爲返回值,賦值給f1變量。執行f1(15)時,用到了fn做用域下的max變量的值。

  • 函數做爲參數被傳遞
var max = 10;
var fn = function(x) {
  if(x > max){
    console.log(x)
  }
}

(function(f) {
  var max = 10;
  f(15)
})(fn)

複製代碼

如上代碼中,fn函數做爲一個參數被傳遞進入另外一個函數,賦值給f參數。執行f(15)時,max變量的取值是10,而不是100。

閉包能夠用在許多地方。它的最大用處有兩個,一個是前面提到的能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中,不會在f1調用後被自動清除。

爲何會這樣呢?緣由就在於f1是f2的父函數,而f2被賦給了一個全局變量,這致使f2始終在內存中,而f2的存在依賴於f1,所以f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。

補充:JS中的函數定義

JS中定義一個函數,最經常使用的就是函數聲明和函數表達式

Js中的函數聲明是指下面的形式:

function functionName(){  
  
}
複製代碼

函數表達式則是相似表達式那樣來聲明一個函數:

var functionName = function(){  

}
複製代碼

咱們可使用函數表達式建立一個函數並立刻執行它,如:

(function() {
  var a, b    // local variables
  // ... // and the code
})()
複製代碼

()();第一個括號裏放一個無名的函數。

兩者區別:js的解析器對函數聲明與函數表達式並非一視同仁地對待的。對於函數聲明,js解析器會優先讀取,確保在全部代碼執行以前聲明已經被解析,而函數表達式,如同定義其它基本類型的變量同樣,只在執行到某一句時也會對其進行解析,因此在實際中,它們仍是會有差別的,具體表如今,當使用函數聲明的形式來定義函數時,可將調用語句寫在函數聲明以前,然後者,這樣作的話會報錯。

閉包的好處

一、模塊化代碼

使用自執行的匿名函數來模擬塊級做用域

(function(){
        // 這裏爲塊級做用域
 })();
複製代碼

該方法常常在全局做用域中被用在函數外部,從而限制向全局做用域中添加過多的變量和函數影響全局做用域。也能夠減小如閉包這樣的對內存的佔用,因爲匿名函數沒有變量指向,執行完畢就能夠當即銷燬其做用域鏈。

var test = (function(){
    var a= 1;
    return function(){
        a++;
        alert(a);
    }
})();

test();//2
test();//3
複製代碼

實現a的自加,不污染全局。

二、循環閉包

循環給每一個li註冊一個click事件,點擊alert序號。代碼以下:

var aLi = document.getElementByClassName("test");
function showAllNum( aLi ){
    for( var i =0,len = aLi.length ;i<len;i++ ){
        aLi[i].onclick = function(){
        alert( i );//all are aLi.length!
      }
   }
}
複製代碼

點擊後會一直彈出同一個值 aLi.length 而不是123。當點擊以前,循環已經結束,i值爲aLi.length。

利用閉包,建一個匿名函數,將每一個i存在內存中, onclick函數用的時候提取出外部匿名函數的i值。代碼以下:

var aLi = document.getElementByClassName("test");
function showAllNum( aLi ){
    for( var i =0,len = aLi.length ;i<len;i++ ){
       (function(i){
          aLi[i].onclick = function(){
            alert( i );
          }
       })(i);
    }
}
複製代碼

或者:

function showAllNum( aLi ){
    for( var i =0,len = aLi.length ;i<len;i++ ){
        aLi[i].onclick = (function(i){
           return function(){
              alert( i );
           }
       })(i);
    }
}
複製代碼

解釋:實際上只是經過函數的賦值表式方式付給了標籤點擊事件,並無運行;當遍歷完後,i變成標籤組的長度,根據做用域的原理,向上找到for函數裏的i,因此點擊執行的時候都會彈出標籤組的長度。閉包可使變量長期駐紮在內存當中,咱們在綁定事件的時候讓它自執行一次,把每一次的變量存到內存中;點擊執行的時候就會彈出對應本做用域i的序號。

閉包的做用

  • 能夠減小全局變量的對象,防止全局變量過去龐大,致使難以維護

  • 防止可修改變量,由於內部的變量外部是沒法訪問的,而且也不可修改的。安全

  • 能夠讀取函數內部的變量,能夠把變量始終保存在內存中,不會在外層函數調用後被自動清除

閉包的優勢

  • 變量長期駐紮在內存中

  • 避免全局變量的污染

  • 私有成員的存在

閉包的特性

  • 函數套函數

  • 內部函數能夠直接使用外部函數的局部變量或參數

  • 變量或參數不會被垃圾回收機制回收GC

閉包的缺點

常駐內存,會增大內存的使用量,使用不當會形成內存泄露,詳解:

一、因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。

二、閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象object使用,把閉包看成它的公用方法Public Method,把內部變量看成它的私有屬性private value,這時必定要當心,不要隨便改變父函數內部變量的值。

閉包在IE下內存泄露問題

IE9以前,JScript對象和COM對象使用不一樣的垃圾收集例程,那麼閉包會引發一些問題。

建立一個閉包,然後閉包有建立一個循環引用,那麼該元素將沒法銷燬。常見的就是dom獲取的元素或數組的屬性(或方法)再去調用本身屬性等。例如:

function handler(){
    var ele = document.getElementById("ele");
    ele.onclick = function(){
        alert(ele.id);
    }
}
複製代碼

閉包會引用包含函數的整個活動對象,便是閉包不直接引用ele,活動對象依然會對其保存一個引用,那麼設置null就能夠斷開保存的引用,釋放內存。代碼以下:

function handler(){
    var ele = document.getElementById("ele");
    var id = ele.id;
    ele.onclick = function(){
        alert(id);
    }
    ele = null;
}
複製代碼

閉包的工做原理

由於閉包只有在被調用時才執行操做,因此它能夠被用來定義控制結構。多個函數可使用同一個環境,這使得他們能夠經過改變那個環境相互交流。

相關文章
相關標籤/搜索