最近打算換工做,因此參加了幾回面試(國內比較知名的幾家互聯網公司)。在面試的過程當中每當被問起閉包,我都會說閉包是做用域的問題?使人驚訝的是幾乎無一例外的當我提到做用域時我都被打斷,並提醒我好好的找一本javascript的書籍看看。而當我忍不住去問面試官對於閉包你是怎麼理解的?我獲得的大可能是回答都是經過返回一個函數,而後經過這個函數來訪問局部變量(私有變量),有的還會扯上聲明提早,this指向等。聽到這些,心理默默滴血,沒錯,我只是菜鳥。javascript
看到有這麼多人不理解閉包,因此我不得再也不次的說起,若是有誤,歡迎指正。html
先從阮一峯09年寫的一篇關於閉包的文章開始(原文地址)文中說"能夠把閉包理解爲就是可以讀取其餘函數內部變量的函數,由於js中,只有函數內部的子函數才能讀取局部變量,所以也能夠把閉包定義在一個函數內部的函數。因此閉包本質就是將函數內部和函數外部鏈接起來的一座橋樑"。java
畢竟不是專業學習js的,也不是程序語言方面的專家,在這裏就不去計較了,下文會給出更完整的閉包說明。(PS:我的仍是比較欣賞阮一峯寫的文章的,能將一些概念講得通俗易懂)python
最後還留下了一個思考題面試
示例01:ruby
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());
示例02:數據結構
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()());
看完這道題,就但願你們將this和閉包分開,不要給本身找麻煩?閉包
在開始說閉包以前,須要理解好如下的概念:函數
詞法做用域(靜態做用域)學習
函數上下文
以前簡單的提過,詞法做用域簡單的理解就是函數的上下文是在聲明是肯定的,而不是在調用時肯定的。這裏不想對這兩個名詞做過多的解釋,咱們知道js/ruby/python等主流的語言都是詞法做用域就好,由於與之相對的動態做用域有許多的問題,因此如今的語言基本都是詞法做用域的。
函數上下文就簡單的理解爲函數執行的環境好了,在這個環境中保存了函數執行所需的變量。
JavaScript採用詞法做用域,也就是說函數的執行依賴於變量的做用域,這個做用域是在函數定義時決定的,而不是函數調用時決定的。爲了實現詞法做用域,JavaScript函數對象的內部狀態不只包含函數的代碼邏輯,還必須引用當前的做用域鏈。函數對象能夠經過做用域鏈相互關聯起來,函數體內部的變量均可以保存在函數做用域內,這種特性在計算機科學文獻中稱爲"閉包"。
當定義一個函數時,它實際上保存了一個做用域鏈。當調用這個函數時,它建立
一個新的對象來存儲它的局部變量,並將這個對象添加到保存的做用域鏈上。
(閉包能夠簡單的理解爲函數用來存儲它的局部變量的對象,這個對象咱們來講是不可見的,是js解釋器實現的過程當中纔會用到的。每個函數都會有這樣的一個對象,做用域鏈則是這些對象之間的關係。)
"閉包"這個術語的來源:指函數變量能夠被隱藏於做用域鏈以內,所以看起來是函數將變量"包裹"了起來。
看文字可能會比較繞,下面是書中的一個例子:
var scope = "global scope"; /*全局變量*/ function checkscope(){ var scope = "local scope"; /*局部變量*/ function f(){return scope;} return f(); } checkscope(); /*=>"local scope"*/
var scope = "global scope"; /*全局變量*/ function checkscope(){ var scope = "local scope"; /*局部變量*/ function f(){return scope;} return f; } checkscope()(); /*=>"local scope"*/
checkscope最後的返回值都是同樣的,即"local scope"。
上面兩個例子的不一樣之處就是函數f執行的地方不一樣,一個在checkscope這個做用域裏調用的(每個函數都是一個做用域),一個在全局做用域裏調用的。回憶以前說的,函數調用時的上下文或者說做用域是在聲明時肯定的,因此與調用的位置無關,即f函數的調用位置雖然不一樣,調用的環境雖然不一樣,但最終的結果都是同樣的。
說到這裏,閉包就說得差很少了,回過頭來看一下常規的對於閉包的理解:
經過返回函數的形式取得函數的局部變量。
這種說法自己沒有錯,但它只是閉包的一種表現形式,
好比將上例進行下更改:
var scope = "global scope"; /*全局變量*/ function checkscope(fn){ var scope = "local scope"; /*局部變量*/ function f(){return scope;} fn(f); } checkscope(function(func){ var scope = "func scope"; return func(); }); /*=>"local scope"*/
改爲相似回調的執行方式,結果仍是同樣的,注意結果並非func scope,可是並無返回f函數這一說,難道這就不是閉包了嗎?(固然這裏有點扣字眼)
想起最開始時的那個思考題了嗎?與閉包就沒什麼關係(注:任何一個函數其實都用到了閉包,但咱們暫且考慮兩層以及兩層以上的嵌套狀況,未嵌套狀況下由於使用的都是全局做用域,結果應該是很直觀的)。this通常用來表明函數的調用對象,它和上下文對象並非同一個,上下文對象對咱們來講是不可見的,除了全局做用域。
示例01:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); /*The Window*/
示例02:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; var that = {name: 'xiaofu'}; /*干擾項*/ alert(object.getNameFunc()()); /*My Object*/
咱們再來看一下題目,示例01輸出的是"The Window",示例02輸出的是"My Object"。
說明:
示例01最終執行的是 function(){return this.name},由於沒有顯示指明調用對象,因此其this執行全局對象。
示例02先調用object.getNameFunc(),由於顯示的指定了調用對象,因此內部的this是object(注:這裏說的是this指向的問題,尚未說閉包),接着執行function (){return that.name},這個函數在getNameFunc這個函數做用域中聲明的,因此它調用的時候使用的是這個做用域,即獲得var that = this;的這個that;而不是外面的that。
真不知道這兩個不相關的東西怎麼會被等同起來看待,之後誰告訴我它們是同一個東西,我就想問了,ruby、python這種沒有原型概念的語言難道就沒有做用域鏈了嗎?
更有甚者將變量聲明提高和閉包混在一塊兒,也是醉了。
閉包:函數執行時變量的獲取從聲明的做用域處去獲取(注意鏈式關係,當前沒有就往父級找,知道全局做用域)
this:顯示指定調用者則this就指向誰,如未指定則爲全局對象