javascript閉包(Closure)
所謂「閉包」,指的是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。javascript
上面是官方的解釋,但這解釋只會讓人頭暈。要理解閉包,首先理解兩點:變量的做用域以及做用域鏈,這兩個在前面都已經介紹過了,而且舉了簡單了列子,來回顧一下:java
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; //在 swapColors函數裏面能夠訪問tempColor,anotherColor和color } //在這裏能夠訪問anotherColor和color,但不能訪問tempColor swapColors(); }
上面的代碼,咱們以前用來講明做用域鏈,也就是變量從裏往外找變量,這一簡單的概念。可是,如今反過來,若是,咱們須要在一個外部執行環境裏面,訪問內部執行環境的變量怎麼辦呢?閉包
function f1(){ var n=999; function f2(){ alert(n); // 999 } //f2(); }
上面的代碼很簡單,咱們在f1裏面去調用f2()固然能夠彈出n的值999,可是若是咱們在外部全局執行環境裏面還要得到n的值,這又該怎麼辦?這就是剛剛的提問,外部的執行環境要求訪問內部執行環境的值。 從外往裏訪問,這裏就能夠經過閉包來實現這個效果,把上面的代碼稍做修改:函數
function f1(){ var n=999; //私有變量 //在函數f1內定義另外的函數做爲f1的方法函數 function f2(){ alert(n); //引用外層函數f1的臨時變量n } return f2; //返回內部函數 } //調用函數 var result=f1(); result(); // 999
當其中一個這樣的內部函數在包含它們的外部函數以外被調用時,就會造成閉包(示例中,調用函數的時候,result實際調用的是f2函數,也就是說f1的一個內部函數f2在f1以外被調用,這時就建立了一個閉包)。spa
在看一個簡單例子:code
function foo() { var a = 1; function geta() { a++; return a; } return geta } myfunc = foo() myfunc() // return 2 myfunc() //return 3
上面只是一個說明閉包的例子,在實際應用中並不常見。下面咱們來看看實際應用中會遇到閉包的狀況blog
狀況一 界面上有一組a標籤,分別點擊,但願點擊不一樣的a標籤彈出不一樣的結果,代碼以下:ip
<ul> <li><a href="#">第0個連接</a></li> <li><a href="#">第1個連接</a></li> <li><a href="#">第2個連接</a></li> <li><a href="#">第3個連接</a></li> </ul> var as = document.getElementsByTagName("a"); for(var i=0;i<as.length;i++){ as[i].onclick = function(){ alert("你如今單擊的是第" + i + "個連接"); } }
上面這段代碼的估計你們都遇到過相似的,想要的效果和實際出現的效果不一致,原本想點擊每個,彈出不一樣的i的值,可是沒想到最後彈出的i的值都是4。 這正是因爲變量的做用域鏈影響形成的,由於在用戶單擊a標籤的時候纔會調用onclick指向的匿名函數。而調用時,須要獲得變量i的值,js解析程序首 先會在匿名函數內部查找i,可是沒有定義。因而往外找,在外部執行環境中找到變量i,可是這個i已經被循環到4了,由於for循環在頁面初始化的時候已經 被執行了。因此匿名函數裏面的i實際上取得的是外部做用域鏈中i的值。想要改正這個錯誤,其實就是把i的值傳入到匿名函數值就好了。可是匿名函數又不能傳 值。這個時候,咱們就能夠用閉包。 先建立這樣一個函數:內存
function closureTest(num){ return function(){ alert("你如今單擊的是第" + num + "個連接") } }
這個函數直接返回了一個匿名函數,以前講過閉包,因此,當返回的這個函數在外部被接收的時候,外層函數closureTest裏的變量num,就會被保存起來,因此,將以前循環的代碼修改一下:作用域
for(var i=0;i<as.length;i++){ as[i].onclick = closureTest(i); }
循環裏面onclick調用的函數closureTest,並傳入了參數i,而closureTest返回了匿名函數,因此根據閉包的原理,傳入的參數i,就被保存在了內存當中,外部訪問的就是每次不同的值了。固然,你也能夠直接寫成下面這個樣子:
for(var i=0;i<as.length;i++){ as[i].onclick = (function(i){ return function(){ alert("你如今單擊的是第" + i + "個連接") } })(i); }
狀況二 利用閉包巧妙地傳遞參數,好比,有這樣的場景,點擊標籤,而後延遲彈出一句話,而而這句話,是用參數傳遞過去的。先看下面的代碼:
<a href="#">點擊我</a> var link = document.getElementById("myLink"); link.onclick = function(){ setTimeout(function(){ alert("你點擊了點擊個人超連接!"); },1000); }
這段代碼,就是點擊1秒後,彈出"你點擊了點擊個人超連接!",固然這裏是直接把話寫在了function裏面,若是這段話是從外面傳過來的呢?好比有個方法
function someFunction(words){ alert(words); } var link = document.getElementById("myLink"); link.onclick = function(){ setTimeout(someFunction,1000); };
這裏的話,問題就來了。怎麼往someFunction傳遞參數呢?這個也能夠直接用閉包
function someFunction(words){ return function(){ alert(words); }; } var link = document.getElementById("myLink"); link.onclick = function(){ var saySomething = someFunction("你點擊了點擊個人超連接!"); setTimeout(saySomething,1000); };
在動態執行環境中,數據實時地發生變化,爲了保持這些非持久型變量的值,咱們用閉包這種載體來存儲這些動態數據。這就是閉包的做用。也就說遇到須要存儲動態變化的數據或將被回收的數據時,咱們能夠經過外面再包裹一層函數造成閉包來解決。