Lua 学习 chapter22

chapter22

Posted by Jow on July 30, 2019

目录

  1. 具有动态名称的全局变量
  2. 非全局环境
  3. 环境和模块
  4. _ENV和load

只有疯狂过,你才知道自己究竟能不能成功。

具有动态名称的全局变量

在lua中,所有的全局变量都被存在_G中,通过_G[name]可以访问到任意一个全局变量。在lua中,全局变量不需要声明就可以直接使用,但是这个可能会造成非常难以查询的bug,所以我们可以对全局变量进行简单的封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
setmetatable(_G, {
    __newindex = function(_, n)
        error("attempt to write to undeclared variable" .. n, 2)
    end,
    __index = function(_, n)
        error("attempt to read undeclared variable" .. n, 2)
    end
})
--这样我们怎么对全局变量初始化呢?使用rawset方法。
function declare(name, initval)
    rawset(_G, name, initval or false)
end

--改版后的
local declaredNames = {}
setmetatable(_G, {
    __newindex = function(t, n, v)
        if not declaredNames[n] then
            local w = debug.getinfo(2, "S").what
            if w ~= "main" and w ~= "C" then
                error("attempt to write to undeclared variable" .. n, 2)
            end
            declaredNames[n] = true
        end
        rawset(t, n, v)
    end,
    __index = function(_, n)
        if not declaredNames[n] then
            error("attempt to read undeclared variable" .. n, 2)
        else
            return nil
        end
    end
})

非全局环境

lua语言中处理全局变量的方式:

  • 编译器在编译所有代码之前,在外层创建局部变量_ENV
  • 编译器将所有自由名称变换为_ENV.var;
  • 函数load(or loadfile)使用全局环境初始化代码段的第一个上值,即lua语言内部维护的一个普通表。

_ENV只是一个普通的变量,将其赋值为nil会使得后续的代码不能直接访问全局变量。

我们还可以使用_ENV来绕过局部声明的变量,直接访问全局变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
local print, sin = print, math.sin
_ENV = nil
print(13)
print(sin(13))
print(cos(13)) --error 访问不到全局

a = 13
local a = 12
print(a)
print(_ENV.a) --访问全局的a,当然也可以使用_G来访问全局的a

_ENV = {}
a = 1
print(a) --print is a nil

a = 15
_ENV = {g = _G}
a = 1
g.print(_ENV.a, g.a} -- 1 , 15

通常_G和_ENV指向的是同一个表。但是,尽管如此,他们是很不一样的实体。_ENV是一个局部变量,所以对“全局变量”的访问实际上访问的都是_ENV。_G则是一个在任何情况下都没有任何特殊状态的全局变量。_ENV永远指向的是当前的环境;而假设在可见且无人改变过其值的前提下,_G通常指向的是全局变量。

_ENV的主要作用就是改变当前的环境。

这相当于一个chunk一个_ENV,然后我们可以通过屏蔽_EVN,来构造一个非全局环境。这里有个关键点,就是_ENV是个局部变量,还是一个upvalue。至于chunk要再了解一下。至于chunk我们可以理解为程序一次执行了代码块为多少,那么这么多的代码块就为一个chunk,例如,在我们游戏中,每次都是通过require来加载一个文件的,那么每个文件就对应一个chunk。

环境和模块

为了防止污染全局环境:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local M = {}
_ENV = {}

function hello()
	print("hello")
end

function sayHello()
	hello() --M.hello
end

local M = {}
local sqrt = math.sqrt
local in = io
_ENV = nil
--这样就不能进行外部访问了


load函数通常被加载代码段的上值_ENV初始化为全局变量。

_ENV和load

函数load通常把加载代码段的上值_ENV初始化为全局变量。不过,函数load还有一个可选的第四个参数来让我们为_ENV指定一个不同的初始值。

1
2
3
4
5
6
7
--file "config.lua"
width = 200
height = 100

--加载文件
env = {}
loadfile("config","t",env)()

配置文件中的代码会运行在空的环境env中,类似于某种沙盒。特别的,所有的定义都会进入这个环境中。即使出错,配置文件也无法影响任何别的东西。