但凡讀書,或者學一門技術,都要問本身如下幾個問題。javascript
我下面就試着從這幾個方向來闡述閉包這個概念。html
在瞭解閉包以前,咱們須要瞭解幾個概念。本文在這裏只作簡單介紹,如須要進一步瞭解,請參考文章末尾的連接。java
變量和函數的可做用範圍,分爲局部做用域和全局做用域。Javascript不具備塊級做用域,而具備函數做用域。node
變量和函數有權訪問的其餘數據。git
每一個函數在執行的時候,會把它的執行環境推入一個棧中,在函數執行完畢後執行環境出棧並被銷燬。保存在其中的全部函數和比變量定義隨之銷燬,控制權返回到以前的執行環境中。全局的執行環境在應用程序退出(瀏覽器關閉)纔會被銷燬。github
做用域鏈用於保證對執行環境有權訪問的變量和函數的有序訪問。面試
閉包這個概念,在函數式編程裏很常見,簡單的說,就是使內部函數能夠訪問定義在外部函數中的變量。嚴格一點的定義是編程
在函數內聲明另外一個函數,而且返回這個函數。這個返回的函數和它的執行環境總體叫作閉包。
讓咱們來看一個例子:瀏覽器
function f1(){ var val = 10; } console.log(val); //Uncaught ReferenceError: val is not defined(…)
因爲從函數外部沒法訪問函數內部的變量,因此報出了錯誤。那麼如何可以訪問到局部做用域的變量呢?閉包
function f1(){ var val = 10; function f2(){ console.log(val); } return f2; } var f2 = f1(); f2(); // 10
在這段代碼中,f2 函數和其執行環境構成了一整個閉包。對於常規的 f1() 方法, 在其內部的變量 val 應該在 f1() 方法執行完畢之後就被垃圾回收。可是 f1() 返回了一個新的方法 f2()。因爲 f2() 訪問了其外部函數的變量 val,val就構成了f2函數的執行環境。val 存在於f2的做用域鏈中,只要f2()方法沒有被銷燬,其做用域鏈中的變量和函數就不會被銷燬, val 也就會一直存在。
for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }, 5); }
上面這個代碼塊會打印五個 5
出來,而咱們預想的結果是打印 1 2 3 4 5。
之因此會這樣,是由於 setTimeout 中的 i 是對外層 i 的引用。當 setTimeout 的代碼被解析的時候,運行時只是記錄了 i 的引用,而不是值。而當 setTimeout 被觸發時,五個 setTimeout 中的 i 同時被取值,因爲它們都指向了外層的同一個 i,而那個 i 的值在迭代完成時爲 5,因此打印了五次 5
。
爲了獲得咱們預想的結果,咱們能夠把 i 賦值成一個局部的變量,從而擺脫外層迭代的影響。
for (var i = 0; i < 5; i++) { (function (idx) { setTimeout(function () { console.log(idx); }, 5); })(i); }
假如咱們要實現一系列的函數:add10,add20。咱們爲此構造了一個名爲 adder 的構造器,以下:
var adder = function (x) { var base = x; return function (n) { return n + base; }; }; var add10 = adder(10); console.log(add10(5)); var add20 = adder(20); console.log(add20(5));
每次調用 adder 時,adder 都會返回一個函數給咱們。咱們傳給 adder 的值,會保存在一個名爲 base 的變量中。因爲返回的函數在其中引用了 base 的值,因而 base 的引用計數被 +1。當返回函數不被垃圾回收時,則 base 也會一直存在。
因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
請定義這樣一個函數
function repeat (func, times, wait) { } // 這個函數能返回一個新函數,好比這樣用 // var repeatedFun = repeat(alert, 10, 5000) // 調用這個 repeatedFun ("helloworld") // 會alert十次 helloworld, 每次間隔5秒
代碼參見:JS bin 閉包面試題一
寫一個函數stringconcat, 要求能
var result1 = stringconcat("a", "b") result1 = "a+b" var stringconcatWithPrefix = stringconcat.prefix("helloworld"); var result2 = stringconcatWithPrefix("a", "b") result2 = "helloworld+a+b"
代碼參見:JS bin 閉包面試題二
參考:
學習Javascript閉包(Closure)
node-lessons/lesson11 at master · alsotang/node-lessons · GitHub
JavaScript做用域鏈