理解javascript函數式編程中的閉包(closure),javascriptclosure
閉包(closure)是函數式編程中的概念,出現於 20 世紀 60 年代,最早實現閉包的語言是 Scheme,它是 LISP 的一種方言。之後閉包特性被其他語言廣泛吸納。
閉包的嚴格定義是“由函數(環境)及其封閉的自由變數組成的集合體。”這個定義對於大家來說有些晦澀難懂,所以讓我們先通過例子和不那麼嚴格的解釋來說明什麼是閉包,然後再舉例說明一些閉包的經典用途。
什麼是閉包
通俗地講, JavaScript 中每個的函數都是一個閉包,但通常意義上嵌套的函數更能夠體
現出閉包的特性,請看下面這個例子:
var generateClosure = function() {var count = 0;var get = function() {count ++;return count;};return get;};var counter = generateClosure();console.log(counter()); // 輸出 1console.log(counter()); // 輸出 2console.log(counter()); // 輸出 3
這段代碼中, generateClosure() 函數中有一個局部變數count, 初值為 0。還有一個叫做 get 的函數, get 將其父範圍,也就是 generateClosure() 函數中的 count 變數增加 1,並返回 count 的值。 generateClosure() 的傳回值是 get 函數。在外部我們通過 counter 變數調用了 generateClosure() 函數並擷取了它的傳回值,也就是 get 函數,接下來反覆調用幾次 counter(),我們發現每次返回的值都遞增了 1。
讓我們看看上面的例子有什麼特點,按照通常命令式編程思維的理解, count 是generateClosure 函數內部的變數,它的生命週期就是 generateClosure 被調用的時期,當 generateClosure 從調用棧中返回時, count 變數申請的空間也就被釋放。問題是,在 generateClosure() 調用結束後, counter() 卻引用了“已經釋放了的” count變數,而且非但沒有出錯,反而每次調用 counter() 時還修改並返回了 count。這是怎麼回事呢?
這正是所謂閉包的特性。當一個函數返回它內部定義的一個函數時,就產生了一個閉包,閉 包 不 但 包 括 被 返 回 的 函 數 , 還包括這個函數的定義環境。上面例子中,當函數generateClosure() 的內建函式 get 被一個外部變數 counter 引用時, counter 和generateClosure() 的局部變數就是一個閉包。如果還不夠清晰,下面這個例子可以協助
你理解:
var generateClosure = function() {var count = 0;var get = function() {count ++;return count;};return get;};var counter1 = generateClosure();var counter2 = generateClosure();console.log(counter1()); // 輸出 1console.log(counter2()); // 輸出 1console.log(counter1()); // 輸出 2console.log(counter1()); // 輸出 3console.log(counter2()); // 輸出 2
上面這個例子解釋了閉包是如何產生的:counter1 和 counter2 分別調用了 generateClosure() 函數,產生了兩個閉包的執行個體,它們內部引用的 count 變數分別屬於各自的運行環境。我們可以理解為,在generateClosure() 返回 get 函數時,私下將 get 可能引用到的 generateClosure() 函數的內部變數(也就是 count 變數)也返回了,並在記憶體中產生了一個副本,之後 generateClosure() 返回的函數的兩個執行個體 counter1和 counter2 就是相互獨立的了。
閉包的用途
1、嵌套的回呼函數
閉包有兩個主要用途,一是實現嵌套的回呼函數,二是隱藏對象的細節。讓我們先看下面這段程式碼範例,瞭解嵌套的回呼函數。如下代碼是在 Node.js 中使用 MongoDB 實現一個簡單的增加使用者的功能:
exports.add_user = function(user_info, callback) {var uid = parseInt(user_info['uid']);mongodb.open(function(err, db) {if (err) {callback(err); return;}db.collection('users', function(err, collection) {if (err) {callback(err); return;}collection.ensureIndex("uid", function(err) {if (err) {callback(err); return;}collection.ensureIndex("username", function(err) {if (err) {callback(err); return;}collection.findOne({uid: uid}, function(err) {if (err) {callback(err); return;}if (doc) {callback('occupied');} else {var user = {uid: uid,user: user_info,};collection.insert(user, function(err) {callback(err);});}});});});});});};
如果你對 Node.js 或 MongoDB 不熟悉,沒關係,不需要去理解細節,只要看清楚大概的邏輯即可。這段代碼中用到了閉包的層層嵌套,每一層的嵌套都是一個回呼函數。回呼函數不會立即執行,而是等待相應請求處理完後由請求的函數回調。我們可以看到,在嵌套的每一層中都有對 callback 的引用,而且最裡層還用到了外層定義的 uid 變數。由於閉包機制的存在,即使外層函數已經執行完畢,其範圍內申請的變數也不會釋放,因為裡層的函數還有可能引用到這些變數,這樣就完美地實現了嵌套的非同步回調。
2、實現私人成員
我們知道, JavaScript 的對象沒有私人屬性,也就是說對象的每一個屬性都是曝露給外部的。這樣可能會有安全隱患,譬如對象的使用者直接修改了某個屬性,導致對象內部資料的一致性受到破壞等。 JavaScript通過約定在所有私人屬性前加上底線(例如_myPrivateProp),表示這個屬性是私人的,外部對象不應該直接讀寫它。但這隻是個非正式的約定,假設對象的使用者不這麼做,有沒有更嚴格的機制呢?答案是有的,通過閉包可以實現。讓我們再看看前面那個例子:
var generateClosure = function() {var count = 0;var get = function() {count ++;return count;};return get;};var counter = generateClosure();console.log(counter()); // 輸出 1console.log(counter()); // 輸出 2console.log(counter()); // 輸出 3
我們可以看到,只有調用 counter() 才能訪問到閉包內的 count 變數,並按照規則對其增加1,除此之外決無可能用其他方式找到 count 變數。受到這個簡單例子的啟發,我們可以把一個對象用閉包封裝起來,只返回一個“訪問器”的對象,即可實現對細節隱藏。
以上就是本文的全部內容,希望能夠協助大家更好的學習理解javascript閉包。
您可能感興趣的文章:
- Javascript 中的類和閉包
- javascript深入理解js閉包
- 前端開發必須知道的JS之閉包及應用
- JavaScript 匿名函數(anonymous function)與閉包(closure)
- js bind 函數 使用閉包儲存執行內容
- JS中批量給元素繫結事件程序中的相關問題使用閉包解決
- 深入Javascript函數、遞迴與閉包(執行環境、變數對象與範圍鏈)使用詳解
- javascript閉包傳參和事件的迴圈綁定樣本探討
- 詳解js閉包
- 詳談JavaScript 匿名函數及閉包