摘要:閉包在計算機科學中,閉包是詞法閉包的簡稱,是引用了自由變量的函數。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。通過閉包完成了私有的成員和方法的封裝。
閉包
在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。 --- 維基百科
其實這段引用已經說明了閉包的本質:引用了自由變量的函數,自由變量將和這個函數一同存在——這是理解閉包的關鍵。
一 原理解釋函數式編程語言的基礎是lambda演算,而閉包又是從函數式編程衍生而來。下面先從Lambda演算理解下函數式思維。(不感興趣可直接跳過)
Lambda演算非形式化的描述Lambda演算是一套用于研究函數定義、應用和遞歸的形式系統。它包括一條變換規則(變量替換)和一條函數定義方式,Lambda演算之通用在于,任何一個可計算函數都能用這種形式來表達和求值。因而,它是等價于圖靈機的。盡管如此,Lambda演算強調的是變換規則的運用,而非實現它們的具體機器。可以認為這是一種更接近軟件而非硬件的方式。Lambda演算對函數式編程語言有巨大的影響,比如Lisp和Haskell。
在lambda演算中,每個表達式都代表一個函數,這個函數有一個參數,并且返回一個值。不論是參數和返回值,也都是一個單參的函數。可以這么說,lambda演算中,只有一種“類型”,那就是這種單參函數。
注:在函數式編程語言中,函數可是一等公民。
函數是通過λ表達式匿名地定義的,這個表達式說明了此函數將對其參數進行什么操作。例如,“加2”函數f(x)= x + 2可以用lambda演算表示為λx.x + 2 (或者λy.y + 2,參數的取名無關緊要)
λ演算中函數只有一個參數,那有兩個參數的函數怎么表達呢?可以通過lambda演算這么表達:一個單一參數的函數的返回值又是一個單一參數的函數(閉包嗎?)。
例如,函數f(x, y) = x + y可以寫作:
λx.λy.x + y ----->λx. (λ y. + x y)
上面這個轉化就叫currying,它展示了,我們如何實現加法(假設+這個符號已經具有相加的功能)。
其實就是我們現在意義上的閉包——你調用一個函數,這個函數返回另一個函數,返回的函數中存儲保留了調用函數的變量。currying是閉包的鼻祖。(如果理解困難,下面會用編程語言實現上面的演算)
閉包解釋閉包被廣泛使用于函數式編程語言,慢慢很多命令式語言也開始支持閉包。在函數中可以(嵌套)定義另一個函數時,如果內部的函數引用了外部的函數的變量,則可能產生閉包。運行時,一旦外部的函數被執行,一個閉包就形成了,閉包中包含了內部函數的代碼,以及所需外部函數中的變量的引用。
典型的支持閉包的語言中,通常將函數當作第一類對象——在這些語言中,函數通常有下列特性:
- 可以將函數賦值給一個變量
- 函數可以作為參數傳遞
- 函數的返回值可以是一個函數
例如以下Scheme(Lisp的一個方言)代碼:
(define (f x) (lambda (y) (+ x y)))
在這個例子中,lambda表達式(lambda (y) (+ x y))出現在函數f中。當這個lambda表達式被執行時,Scheme創造了一個包含此表達式以及對x變量的引用的閉包,其中x變量在lambda表達式中是自由變量。
下面是用ECMAScript (JavaScript)寫的同一個例子:
function f(x){ return function(y) { return x + y; }; }
其中f返回的匿名函數與其自由變量x組成了一個閉包。
上述代碼在nodeJS環境中執行:
console.log( (f(7)) (2) ); //9
首先用第一個參數(7)代替最外層函數的參數(x),然后用第二個參數(2)代替第二層函數的參數(y),最終得到計算結果。
注意:這個運算執行了兩個函數:f和匿名函數。f的作用域為(f 7),這就是說,當(f 7)執行后,f這個函數就結束了,而x是f的私有變量,理論上x應該被釋放了,然后x在f函數執行結束后并沒有被釋放,而是繼續被匿名函數繼續使用。支持這種機制的語言稱為支持閉包機制(在函數中可以(嵌套)定義另一個函數時,如果內部的函數引用了外部函數的變量,即使已經離開了外部函數的環境,自由變量(外部函數的變量)也和內部函數一同存在,則產生閉包)。
二 閉包的實現通過上面的原理解釋我們提出了這樣一個問題(雖然這樣的問題不干擾你理解閉包):
如果一個函數定義在棧中,那么當函數返回時,定義在函數中的局部變量就不復存在了, 那為什么內部的函數可以訪問外部函數的變量?即使外部函數執行完,外部函數的變量也能和內部函數一同存在?
下面以JavaScript閉包實現舉例。
javascript
上面是一個使用閉包的簡單示例,代碼執行完畢后,函數對象并不會被垃圾回收機制回收1,函數內的臨時變量能夠得以長期存在,而這個變量只能夠被閉包函數修改,在外部是無法訪問和修改的。(這個其實前面已經說過,這里有點啰嗦)
JavaScript中將作用域鏈描述為一個對象列表,不是綁定的棧。每次調用JavaScript函數的時候(函數也是對象),都會為之創建一個新的對象用來保存局部變量,把這個對象添加至作用域鏈中。
每個函數關聯都有一個執行上下文場景(Execution Context) ,然后執行環境會創建一個活動對象(call object),該對象包含了兩個重要組件,環境記錄,和外部引用(指針)。環境記錄包含了函數內部聲明的局部變量和參數變量,外部引用指向了外部函數對象的上下文執行場景。這樣的數據結構就構成了一個單向的鏈表,每個引用都指向外層的上下文場景。最后形成如下圖的結構:
注:此圖盜用,此圖網站已不能打開。
如上圖和代碼所示:a返回函數b的引用給c,函數b的作用域鏈包含了對函數a的活動對象的引用,也就是說b可以訪問到a中定義的所有變量和函數。函數b被c引用,函數b又依賴函數a,因此函數a在返回后不會被GC回收。
三 閉包的作用以上述代碼為例:
- 保護函數內的變量安全:函數a中i只有函數b才能訪問,而無法通過其他途徑訪問到,因此保護了i的安全;
- 在內存中維持一個變量:函數a中i的一直存在于內存中,因此每次執行c(),都會給i自加1;
- 通過保護變量的安全實現JS私有屬性和私有方法(不能被外部訪問)。
Singleton 單件:(盜用理解Javascript的閉包的例子)
var singleton = function () { var privateVariable; function privateFunction(x) { ...privateVariable... } return { firstMethod: function (a, b) { ...privateVariable... }, secondMethod: function (c) { ...privateFunction()... } }; }();
這個單件通過閉包來實現。通過閉包完成了私有的成員和方法的封裝。匿名主函數返回一個對象。對象包含了兩個方法,方法1可以方法私有變量,方法2訪問內部私有函數。需要注意的地方是匿名主函數結束的地方的"()’,如果沒有這個"()’就不能產生單件。因為匿名函數只能返回了唯一的對象,而且不能被其他地方調用。這個就是利用閉包產生單件的方法。
四 概念混淆之匿名函數匿名函數指的是沒有函數名稱的函數,它和閉包沒有關系,只是閉包中函數可以通過匿名函數編寫,當然匿名函數不止會出現在閉包中。
如下面一個javascript代碼示例:
var f = function(name){ //函數體 };
函數表達式是創建了一個匿名函數,然后將匿名函數賦值給一個變量f。
參考理解Javascript的閉包
閉包漫談(從抽象代數及函數式編程角度)
維基百科-閉包
維基百科-λ演算
編程語言的基石——Lambda calculus
注:Javascript的垃圾回收機制:
在Javascript中,如果一個對象不再被引用,那么這個對象就會被GC回收。如果兩個對象互相引用,而不再被第3者所引用,那么這兩個互相引用的對象也會被回收。因為函數a被b引用,b又被a外的c引用,這就是為什么函數a執行后不會被回收的原因。??
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85785.html
摘要:大名鼎鼎的閉包面試必問。閉包的作用是什么。看到閉包在哪了嗎閉包到底是什么五年前,我也被這個問題困擾,于是去搜了并總結下來。關于閉包的謠言閉包會造成內存泄露錯。閉包里面的變量明明就是我們需要的變量,憑什么說是內存泄露這個謠言是如何來的因為。 本文為饑人谷講師方方原創文章,首發于 前端學習指南。 大名鼎鼎的閉包!面試必問。請用自己的話簡述 什么是「閉包」。 「閉包」的作用是什么。 首先...
摘要:完美的閉包,對,閉包就這么簡單。這僅僅是閉包的一部分,閉包利用函數作用域達到了訪問外層變量的目的。此時一個完整的閉包實現了,的垃圾回收機制由于閉包的存在無法銷毀變量。 1.閉包是指有權訪問另一個函數作用域中的變量的函數。 上面這段話來自 javascript 高級程序設計 第三版 P178 。作者說閉包是一個函數,它有訪問另一個函數作用域中的變量的能力。 2.函數訪問它被創建時所處的...
摘要:到底什么是閉包這個問題在面試是時候經常都會被問,很多小白一聽就懵逼了,不知道如何回答好。上面這么說閉包是一種特殊的對象。閉包的注意事項通常,函數的作用域及其所有變量都會在函數執行結束后被銷毀。從而使用閉包模塊化代碼,減少全局變量的污染。 閉包,有人說它是一種設計理念,有人說所有的函數都是閉包。到底什么是閉包?這個問題在面試是時候經常都會被問,很多小白一聽就懵逼了,不知道如何回答好。這個...
閱讀 1647·2021-11-24 09:39
閱讀 3099·2021-11-22 15:24
閱讀 3096·2021-10-26 09:51
閱讀 3285·2021-10-19 11:46
閱讀 2896·2019-08-30 15:44
閱讀 2223·2019-08-29 15:30
閱讀 2541·2019-08-29 15:05
閱讀 778·2019-08-29 10:55