Lua中類型爲thread,function和userata的對象均可以關聯一個表,稱之爲環境。環境也是一個常規的table。能夠和普通的table同樣進行操做,存放與對象相關的各類變量。程序員
Lua中的全局變量存在放當前函數的環境中,Lua標準庫中的函數如setmetable, string.find等註冊在函數的環境中,在Lua腳本就能夠直接訪問。閉包
Lua代碼中能夠訪問和操做和函數關聯的環境,Lua標準庫爲此提供了兩個方法函數
經過getfenv(0)獲取當前環境後遍歷,能夠查看全部的全局變量。lua
for k, v in pairs(getfenv(0)) do print ("k: ", k, ", v: ", type(v)) end
Lua中定義的變量默認爲全局變量,保存在當前函數的環境中。 以下面的代碼code
local env = getfenv(1) print("var: ", env["var"]) print("loval_var: ", env["loval_var"]) var = "Hello, global variable" local local_var = "Hell, local variable" print("after set") print("var: ", env["var"]) print("loval_var: ", env["loval_var"])
變量var爲全局變量,存放在當前的環境中,經過getfenv().var
能夠訪問,而變量local_var爲局部變量,getfenv().local_var
值爲nil
對象
執行結果字符串
$ lua test3.lua var: nil loval_var: nil after set var: Hello, global variable loval_var: nil
在thread中經過load等方式建立的函數稱爲非嵌套函數,非嵌套函數的默認環境爲此thread的環境。在函數中建立函數時,會將本身的環境設置爲新建立的函數的默認環境。get
訪問全局變量時訪問的是當前所在函數的環境,以下面的代碼虛擬機
local function f() print("env: ", getfenv()) local function f1() print("env f1: ", getfenv()) end local function f2() print("env f2: ", getfenv()) end f1() f2() end f()
執行結果 f,f1,f2三個函數的環境是同一個tablestring
env: table: 0x25a16b0 env f1: table: 0x25a16b0 env f2: table: 0x25a16b0
由於函數f1和f2都是在函數f中建立的,他們的環境的初始值就是函數f的環境。 下面經過setfenv改變函數的環境
local function f() print("env: ", getfenv()) local function f1() print("env f1: ", getfenv()) end local function f2() print("env f2: ", getfenv()) end setfenv(f2, {}) f1() f2() end f()
執行結果以下,getfenv自己就在環境中存放,經過setfenv(f2, {})
將函數f2的環境設置成一個空的table,在函數f2中調用getfenv時因爲getfenv是nil,致使腳本報錯。
env: table: 0x17a66b0 env f1: table: 0x17a66b0 lua: test4.lua:9: attempt to call global 'getfenv' (a nil value) stack traceback: test4.lua:9: in function 'f2' test4.lua:15: in function 'f' test4.lua:18: in main chunk [C]: ?
將函數f的環境經過閉包保存下來,就能夠在函數f2中調用其中的方法。以下所示
local function f() print("env: ", getfenv()) local function f1() print("env f1: ", getfenv()) end local env = getfenv() local function f2() local e = env e.print("env f2: ", e.getfenv()) end setfenv(f2, {}) f1() f2() end f()
此時能夠看到函數f2的環境與函數f和f1不一樣
$ lua test4.lua env: table: 0x1ebf6b0 env f1: table: 0x1ebf6b0 env f2: table: 0x1ec7480
Lua中的_G是一個指向全局環境(thread的環境)的全局變量。詳細一點能夠這樣理解
print(_G)
, 打印的是全局環境,也是此函數的環境。輸出_G和_G._G,值徹底相同
$ cat test.lua print(_G) print(_G._G) $ lua test.lua table: 0x1d0c6b0 table: 0x1d0c6b0
若是經過setfenv設置了環境,新的環境是沒有_G這個變量的。
local function f() local function f1() print("f1: ", _G) end local env = getfenv() local function f2() local e = env e.print("f2: ", _G) end setfenv(f2, {}) f1() f2() end f()
執行結果
f1: table: 0xf916b0 f2: nil
調用print輸出字符串print("Hello")
,能夠經過luac查看編譯後的lua虛擬機的指令
$ luac -l -l test.lua main <test.lua:0,0> (4 instructions, 16 bytes at 0xd92530) 0+ params, 2 slots, 0 upvalues, 0 locals, 2 constants, 0 functions 1 [1] GETGLOBAL 0 -1 ; print 2 [1] LOADK 1 -2 ; "Hello" 3 [1] CALL 0 2 1 4 [1] RETURN 0 1 constants (2) for 0xd92530: 1 "print" 2 "Hello" locals (0) for 0xd92530: upvalues (0) for 0xd92530:
這裏只關注GETGLOBAL這條指令,print是全局變量,須要經過GETGLOBAL取到這個變量的值
對於GETGLOBAL,Lua虛擬機的執行以下(以lua5.1爲例),在函數luaV_execute中
case OP_GETGLOBAL: { TValue g; TValue *rb = KBx(i); sethvalue(L, &g, cl->env); lua_assert(ttisstring(rb)); Protect(luaV_gettable(L, &g, rb, ra)); continue; }
注意sethvalue(L, &g, cl->env);
這句,cl表明當前的函數,cl->env指向當前函數的環境。即GETGLOBAL指令取得的是從當前函數的環境中取得變量的值的。
而lua_getglobal這個函數確是直接訪問thread的全局環境,取得全局環境中key爲name
的值。
void lua_getglobal (lua_State *L, const char *name);
一樣都是"getglobal",倒是兩個不一樣的意思,直接讓我迷惑了好久,差點分不清什麼是全局變量了。