深入理解JavaScript——閉包
跟很多新手一樣我也是初入前端,對閉包的理解花費的時間和精力相當的多。效果也還行,今天我就來根據自己的理解細致的講一講閉包,由於是初入學習的時候不免有一些彎路和困惑,我想信這也是很多跟我一樣的人會同樣遇到的問題。我就以自己的學習路徑和遇到的各種坑來談閉包。希望對各位有一定的幫助。(菜鳥,也請各位多多指教)
閉包是什麼?《JavaScript高級程序設計》上面這麼描述的:閉包是指有權訪問另一個函數作用域中的變量的函數。這句話第一次看的時候模模糊糊,似是而非。碰到問題就不會運用了,聽別人的分析頭頭是道,說到底還是沒搞明白。現在我覺得要徹底搞清這句話必須對JavaScript的作用域,匿名函數,甚至JavaScript的編譯原理有一些簡單的了解。經過查閱理解各種資料書籍對閉包的解釋,再回過頭來看了一些源碼,慢慢的有了一點感覺。我覺得對閉包描述最好的一句話是:“閉包是基於詞法作用域書寫代碼時所產生的自然結果,你甚至不需要為了利用它而有意為之的創建閉包,閉包的創建和使用是在你的代碼中隨處可見。你缺少的是根據你自己的意願來識別,擁抱和影響閉包的思維環境。”話有點長但點出來閉包在JavaScript這麼語言中存在的實際價值,大家可以細細體會一下。接下來我已實際例子來講講閉包。
首先看一個簡單的例子:
function createComparisonFunction(propertyName){ return function(obj1,obj2){ var value1=object1[propertyName]; var value2=object2[propertyName]; if(value1<value2){ return -1; }else if(value1>value2){ return 1; }else{ return 0; } } }
例子中返回的是一個匿名函數,其中匿名函數中value1,value2同時又對外部函數createComparisonFunction()的參數propertyName進行調用。再看看上面對閉包的定義:有權訪問另一個函數作用域中的變量的函數。return的匿名函數有權訪問外部函數作用域中的變量propertyName,因此這是一個閉包。但實際來說這只是一個基於詞法作用域的查找規則,很好理解也很自然。
可能有些人不明白什麼是詞法作用域的查找規則:其實說簡單點就是根據變量的作用鏈域來查找並取得該變量。以上例來說:createComparisonFunction函數的作用域包含一個變量property和一個匿名函數(由於沒有函數名其實在createComparisonFunction函數中也無法調用,這也是匿名函數的一個缺點,記得事件監聽函數調用一個匿名函數時是無法移除嗎?道理是一樣的),匿名函數作用域中包括obj1,obj2,value1,value2這四個變量。匿名函數中的變量調用時首先在自己作用域中查詢,找到了該變量就調用,找不到就往外層走接著找,直到全局作用域如果還是找不到就會報ReferenceError(如果找到了一個var a;呢?由於a為undefined所以會報TypeError)。而createComparisonFunction函數則只能在他的作用域中查找,不能去內層的匿名函數中查找,這種查找規則就是詞法作用域的查找規則(當然這不只是基本規則)。
我們再來看一個例子:
function foo() { var a = 2; function bar() { console.log( a ); } return bar; } var baz = foo(); baz(); //2
答案可能很多人都能猜出來但其實現原理是怎樣呢?我現在就來細致的分析一下:首先聲明了一個函數foo其作用域包括變量a和bar函數。foo函數內部有一個函數bar內部沒有新變量。然後定義一個變量baz,其值為foo()而foo()返回bar,顧baz=bar;最後一句baz()即等價於bar();根據詞法作用域的規則bar是在foo函數中聲明的。外部是無法訪問的呀!這正是閉包的神奇之處!我們再一次回到閉包定義:有權訪問另一個函數作用域中的變量的函數,baz()函數有權訪問foo函數中的bar。這就是閉包對吧!搞清閉包後有些人還會疑問?閉包是怎麼形成的,為什麼你說它有權訪問foo中的bar呢?首先閉包的形成是在一個函數內部創建一個函數這是創建閉包的最常見方法。當將內部函數傳遞到它的詞法作用域以外他都會持有對原始定義作用域的引用,執行此函數就會 使用閉包。
閉包的基礎大概就是這幾點,希望大家能仔細體會。抽時間補充點閉包的一些其他用途,來幫大家擴展一下思維。
通過此文,希望大家對閉包的知識掌握,謝謝大家對本站的支持!