在Lua中,閉包(closure)是由一個函數和該函數會訪問到的非局部變量(或者是upvalue)組成的,其中非局部變量(non-local variable)是指不是在局部作用範圍內定義的一個變量,但同時又不是一個全局變量,主要應用在嵌套函數和匿名函數裏,因此若一個閉包沒有會訪問的非局部變量,那麼它就是通常說的函數。也就是說,在Lua中,函數是閉包一種特殊情況。簡而言之,閉包就是一個函數加一個upvalue。那麼接下來看下upvalue是啥。
Lua使用結構體upvalue來實現閉包。外面的局部變量可以直接通過upvalue進行訪問。upvalue最開始的時候指向棧中的一個變量,此時這個變量還在它的生存週期內。當變量離開作用域(譯者注:就是函數返回後,變量的生存週期結束時),這個變量就從棧轉移到了upvalue中。雖然這個變量存儲在upvalue中,但是訪問這個變量還是間接通過upvalue中的一個指針進行的(譯者注:和在棧中時候的訪問方式一樣)。因此,變量位置的轉移對任何試圖讀寫這個變量的代碼都是透明的。有別於這個變量在一個函數內部時候的行爲,函數聲明、訪問這個變量,就是直接對棧的操作。看下具體例子:
- function f1(n)
- --函數參數n也是局部變量
- local function f2()
- print(n) --引用外部函數的局部變量
- end
- return f2
- end
- g1 = f1(2015)
- g1() -- 打印出2015
- g2 = f1(2016)
- g2() -- 打印出2016
這裏的n就是upvalue。upvalue實際指的是變量而不是值,這些變量可以在內部函數之間共享,即upvalue提供一種閉包之間共享數據的方法,再看個例子:
- function Create(n)
- local function foo1()
- print(n)
- end
- local function foo2()
- n = n + 10
- end
- return foo1,foo2
- end
- f1,f2 = Create(2015)
- f1() -- 打印2015
- f2()
- f1() -- 打印2025
- f2()
- f1() -- 打印2035
上面的例子中,閉包f1和f2共享同一個upvalue了,這是因爲當Lua發現兩個閉包的upvalue指向的是當前堆棧上的相同變量時,會聰明地只生成一個拷貝,然後讓這兩個閉包共享該拷貝,這樣任一個閉包對該upvalue進行修改都會被另一個探知。爲什麼會這樣,我們看下面的解釋:
通過爲每個變量最多創建一個upvalue並按需要重複利用這個upvalue,保證了未決狀態(未超過生命週期)的局部變量(pending vars)能夠在閉包之間正確地共享。爲了保證這種唯一性,Lua維護這一條鏈表,該鏈表中每個節點對應一個打開的upvalue(opend upvalue)結構,打開的upvalue是指當前正指向棧局部變量的upvalue,如上圖的未決狀態的局部變量鏈表(the pending vars list)。當Lua創建一個新的閉包時,Lua會遍歷當前函數所有的外部的局部變量,對於每一個外部的局部變量,若在上面的鏈表中能找到該變量,則重複使用該打開的upvalue,否則,Lua會創建一個新的打開的upvalue,並把它插入鏈表中。當局部變量離開作用域時(即超過變量生命週期),這個打開的upvalue就會變成關閉的upvalue(closed upvalue),並把它從鏈表中刪除,一旦某個關閉的upvalue不再被任何閉包所引用,那麼它的存儲空間就會被回收。
最後看下閉包的應用。閉包最常用的一個應用就是實現迭代器。所謂迭代器就是一種可以遍歷一種集合中所謂元素的機制。每個迭代器都需要在每次成功調用之間保持一些狀態,這樣才能知道它所在的位置及如何進到下一個位置。閉包剛好適合這種場景。比如下面的代碼:
- function values(t)
- local i = 0
- return function () i = i + 1 return t[i] end
- end
- t = {10, 20, 30}
- iter = values(t)
- while true do
- local element = iter()
- if element == nil then break end
- print(element)
- end
總結下lua閉包,關鍵點是upvalue,然後注意下如何申明一個揹包,函數(A)裏面返回的是函數(B),B引用了A的局部變量。