文章同步到githubhtml
js的閉包概念幾乎是任何面試官都會問的問題,最近把閉包這塊的概念梳理了一下,記錄成如下文章。java
我先列出一些官方及經典書籍等書中給出的概念,這些概念雖然表達的不同,可是都在對閉包作了最正確的定義和翻譯,也幫助你們更好的理解閉包,這閱讀起來可能比較模糊,你們日後看,本文經過對多個經典書籍中的例子講解,相信會讓你們能很好的理解js中的閉包。文章開始,我會先鋪墊一下閉包的概念和爲何要引入閉包的概念,而後結合例子來講明講解,並講解如何使用閉包。git
閉包包含自由(未綁定到特定對象)變量;這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。「閉包」 一詞來源於如下二者的結合:要執行的代碼塊(因爲自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和爲自由變量提供綁定的計算環境(做用域) -- 百度百科github
函數對象能夠經過做用域鏈互相關聯起來,函數體內部的變量均可以保存在函數做用域內,這種特性在計算機科學中成爲閉包面試
閉包是指有權訪問另外一個函數做用域中的變量的函數。segmentfault
我引入《深刻理解JavaScript系列:閉包(Closures)》文章中的例子來講明,也能夠直接去看那篇文章,我結合其餘書籍反覆讀了不少遍此文章才理解清楚。以下:瀏覽器
function testFn() { var localVar = 10; // 自由變量 function innerFn(innerParam) { alert(innerParam + localVar); } return innerFn; } var someFn = testFn(); someFn(20); // 30
通常來講,在函數執行完畢以後,局部變量對象即被銷燬,因此innerFn是不可能以返回值形式返回的,innerFn函數做爲局部變量應該被銷燬纔對。閉包
這是當函數以返回值時的問題,另外再看一個當函數以參數形式使用時的問題,仍是直接引用《深刻理解JavaScript系列》中的例子,也方便你們有興趣能夠直接去閱讀那篇文章函數
var z = 10; function foo() { alert(z); } foo(); // 10 – 使用靜態和動態做用域的時候 (function () { var z = 20; foo(); // 10 – 使用靜態做用域, 20 – 使用動態做用域 })(); // 將foo做爲參數的時候是同樣的 (function (funArg) { var z = 30; funArg(); // 10 – 靜態做用域, 30 – 動態做用域 })(foo);
當函數foo在不一樣的函數中調用,z該取哪一個上下文中的值呢,這就又是一個問題,因此就引入了閉包的概念,也能夠理解爲定義了一種規則。學習
看一個《javsScript權威指南》中的一個例子,我稍微作一下修改以下:
var scope = 'global scope'; function checkScope() { var scope = 'local scope'; return function() { console.log(scope); } } var result = checkScope(); result(); // local scope checkScope變量對象中的scope,非全局變量scope
分析:
即便匿名函數是在checkScope函數外調用,也沒有使用全局變量scope,便是利用了js的靜態做用域,當匿名函數初始化時,就建立了本身的做用域鏈(做用域鏈的概念這裏不作解釋,能夠參考個人另外一篇文章js中的執行棧、執行環境(上下文)、做用域、做用域鏈、活動對象、變量對象的概念總結,其實當把做用域鏈理解好了以後,閉包也就理解了), 此匿名函數的做用域鏈包括checkScope的活動對象和全局變量對象, 當checkScope函數執行完畢後,checkScope的活動對象並不會被銷燬,由於匿名函數的做用域鏈還在引用checkScope的活動對象,也就是checkScope的執行環境被銷燬,可是其活動對象沒有被銷燬,留存在堆內存中,直到匿名函數銷燬後,checkScope的活動對象纔會銷燬,解除對匿名函數的引用將其設置爲null便可,垃圾回收將會將其清除,另外當外部對checkScope的自由變量存在引用的時候,其活動對象也不會被銷燬
result = null; //解除對匿名函數的引用
註釋:
自由變量是指在函數中使用的,但既不是函數參數也不是函數的局部變量的變量
補充:
引用一下《javsScript權威指南》中的補充,幫助你們進一步理解
當函數以參數形式使用時通常用於利用閉包特性解決實際問題,好比瀏覽器中內置的方法等,下面我直接引用《深刻理解JavaScript系列:閉包(Closures)》中關於閉包實戰部分的例子以下:
在sort的內置方法中,函數以參數形式傳入回調函數,在sort的實現中調用:
[1, 2, 3].sort(function (a, b) { ... // 排序條件 });
和sort的實現同樣
[1, 2, 3].map(function (element) { return element * 2; }); // [2, 4, 6]
var foo = {}; // 初始化 (function (object) { var x = 10; object.getX = function() { return x; }; })(foo); alert(foo.getX()); // 得到閉包 "x" – 10
先來看一個例子
var fnBox = []; function foo() { for(var i = 0; i < 3; i++) { fnBox[i] = function() { return i; } } } foo(); var fn0 = fnBox[0]; var fn1 = fnBox[1]; var fn2 = fnBox[2]; console.log(fn0()); // 3 console.log(fn1()); // 3 console.log(fn2()); // 3
用僞代碼來講明以下:
fn0.[[scope]]= { // 其餘變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3] } fn1.[[scope]]= { // 其餘變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3] } fn2.[[scope]]= { // 其餘變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3], }
分析:
這是由於fn0、fn一、fn2的做用域鏈共享foo的活動對象, 並且js沒有塊級做用域,當函數foo執行完畢的時候foo的活動對象中i的值已經變爲3,當fn0、fn一、fn2執行的時候,其最頂層的做用域沒有i變量,就沿着做用域鏈查找foo的活動對象中的i,因此i都爲3。
可是這種結果每每不是咱們想要的,這時就能夠利用認爲建立一個閉包來解決這個問題,以下:
var fnBox = []; function foo() { for(var i = 0; i < 3; i++) { fnBox[i] = (function(num) { return function() { return num; } })(i); } } foo(); var fn0 = fnBox[0]; var fn1 = fnBox[1]; var fn2 = fnBox[2]; console.log(fn0()); // 0 console.log(fn1()); // 1 console.log(fn2()); // 2
用僞代碼來講明以下:
fn0.[[scope]]= { // 其餘變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3], fn0自己的活動對象AO: {num: 0} } fn1.[[scope]]= { // 其餘變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3], fn1自己的活動對象AO: {num: 1} } fn2.[[scope]]= { // 其餘變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3], fn2自己的活動對象AO: {num: 2} }
分析:
當使用自執行匿名函數建立閉包, 傳入i的值賦值給num,因爲做用域鏈是在函數初始化時建立的,因此當每次循環時,函數fn十、fn一、fn2的做用域鏈中保存了當次循環是num的值, 當fn十、fn一、fn2調用時,是按照自己的做用域鏈進行查找。
從理論的角度將,因爲js做用域鏈的特性,js中全部函數都是閉包,可是從應用的角度來講,只有當函數以返回值返回、或者當函數以參數形式使用、或者當函數中自由變量在函數外被引用時,才能成爲明確意義上的閉包。
最後,我想表達的式,本篇大量引用和羅列了經典的犀牛書《javaScript權威指南》、紅寶書《javaScript高級教程》、以及《深刻理解JavaScript系列:閉包(Closures)》系列文章中的概念和例子,不爲能造成本身的獨特看法,只爲了能把閉包清晰的講解出來。筆者是個小菜鳥,能力實在有限,也在學習中,但願你們多多指點,如發現錯誤,請多多指正。也但願看過此文的朋友能對閉包多一些理解,那我寫這篇文章也就值得了。下次面試時就能夠告訴面試官什麼是閉包了。謝謝。