阅读 50

加载函数

loadfile(filename)

  • 作用:从文件中加载lua代码,但是不会运行代码,只是编译代码,并将编译后的代码作为一个函数返回,而且编译一次可以多次运行。
  • filename为文件名,需要加上后缀
  • 不会引发错误,若文件不存在,即返回nil或错误信息,可以使用assert函数捕获错误
--保存在Set.lua中
local Set = {}
function Set:print()
    return "print"
end
return Set

---查错
loadfile("Set")
--没有反应,因为loadfile只是编译代码,不会运行,所以不会主动终止程序
assert(loadfile("Set"))
-- c:/Users/27238/.vscode/extensions/actboy168.lua-debug-1.22.5/runtime/win64/lua53/lua.exe:
--  D:\Unity\workspace\lua/main.lua:75: cannot open Set: No such file or directory
-- stack traceback:
-- 	[C]: in function 'assert'
-- 	D:\Unity\workspace\lua/main.lua:75: in main chunk
-- 	[C]: in ?

--assert相当于
local s,msg = loadfile("Set")
if not s then
    print(msg)
end

--运行
local set = assert(loadfile("Set.lua"),"no such file") --加载编译文件,以函数形式返回
local set = set() --运行代码段,返回set table
print(set:print()) --运行set中的函数
--print
复制代码

dofile(filename)

  • 加载文件,并运行文件,每次dofile都会重新加载,并运行一次代码。相当于
function dofile(fileName)
    local f = loadfile(fileName)
    return f()
end
复制代码
  • filename为文件的名字,需要后缀
  • 被载入的文件有错,就会报错,且会终止程序,因为dofile不仅编译了代码,还运行了代码
local Set = {}

function Set:print()
    return "print"
end
3 --错误位置
return Set

--调用
dofile("Set.lua")
-- c:/Users/27238/.vscode/extensions/actboy168.lua-debug-1.22.5/runtime/win64/lua53/lua.exe: Set.lua:6:
--  unexpected symbol near '3'
-- stack traceback:
-- 	[C]: in function 'dofile'
-- 	D:\Unity\workspace\lua/main.lua:75: in main chunk
-- 	[C]: in ?

-----修正后
local Set = {}

function Set:print()
    return "print"
end
return Set

local Set = dofile("Set.lua")
print(Set:print())
--print
复制代码

二者对比的话,建议使用loadfile,更灵活, 还可以加载编译一次,做到多次运行,节省时间。

load(chunk:string,chunkname:string,mode:string,env:any)

  • chunk为存有代码段的字符串
  • chunkname在代码段的名字,可以时任何字符串
  • mode为代码端的类型,当等于't'时代表文本,'b'代表二进制,'bt'代表二进制和文本
  • env就是前面所说的_ENV环境,也就是load加载的这些代码段的运行环境,见_G和_ENV
  • 与loadfile不同的事,load从字符串或函数中读取代码端,而不是从文件中,也是会编译代码端,并以函数的形式返回。
local d = load("print(33)") -- 加载编译代码
d() --运行带代码
复制代码
  • chunk为字符串形式的代码段
  • load很强大,但是性能消耗也比较大,相对于其他函数而言,还会引起意想不到的错误。所以在找不到其他解决办法时在使用该函数
  • load也不会引发错误,若load失败时,会返回nil及错误信息

local str = "function func() print(222 + nil)" --一个有错无的函数
local chunk ,msg = load(str)
print(chunk,msg)
-- nil	[string "function func() print(222 + nil)"]:1: 'end' expected near <eof>
复制代码
  • load总是在当前环境中编译代码段(若没有人为设置,就可以说是_G全局环境,因为lua初始化时,没有人为设置,_ENV和_G指向了同一个表;若有认为设置,当前环境就是load函数中传入的_ENV表)
i = 13
local i = 12

local str = " i = i + 1 print(i)"
load(str)() --使用默认的_ENV环境

local env = { i = 244 ,print = print} --定义load函数使用的_ENV环境
load(str,"chunk","bt",env)() --将自定义的_ENV环境传入load函数,加载的代码的运行环境为传入的_ENV环境

local _ENV = {} -- 改变默认的_ENV环境,环境是一个空表,什么都没有
print()

--运行结果
-- 14
-- 245
-- error :attempt to call a nil value (global 'print'),这是因为新的_ENV中是空的,并不存在print函数,调用不存在的函数会引发错误
复制代码

定义了一个全局的i和局部的i,最后load操作的时全局的i

loadstring,dostring lua 5.3已经删除。

  • loadstring用法及注意事项和load一样
  • dostring()相当于
function dostring(str)
    local f = assert(load(str))
    return f()
end
复制代码

require(modename)

  • 参考方案
  • 详细流程 require可以避免模块文件的重复加载。流程大致如下:
  • 当使用require加载一个文件时,会先查找表package.loaded中是否存在相应的模块,若存在,则直接返回,因为一旦一个模块被加载过后,后续所有的require都使用同一个返回值,避免了重复加载文件。提高效率
  • 若没有找到相应模块。lua会从指定的搜索路径(package.path)中搜索指定的文件,若找到了相应的文件,则调用loadfile加载文件,返回一个被称之为加载器的函数。(简单的说是编译了文件里的代码,没有运行)
  • 若还是没有找到。则会c语言标准库(package.cpath)中搜索,如果找到了一个相应的c标准库,则调用loadlib函数加载,这个底层函数会查找名字为luaopen_modename的函数,这时候后的加载器就是loadlib的返回结果,也就是一个被表示为lua函数的c语言函数luaopen_name
  • 之后require带着连个参数调用加载函数(加载器):模块名和加载函数所在的文件名称(也就是文件路径)
  • 如果加载函数有返回值,则将返回值保存在package.loaded中,否则就假设模块的返回值为true,这是为了避免文件被重复加载。
local loaded = {}  --package.loaded
local path = "?.lua" -- package.path
function requires(name)
    if not loaded[name] then
        local d = assert(loadfile((path:gsub("%?",name)))) --加载
        loaded[name] = true
        local res = d()
        if res then
            loaded[name] = res
        end
    end
    return loaded[name]
end

local set = requires("Set")
print(set:print())

-- Set.lua
local Set = {}

function Set:print()
    return "print"
end
return Set
复制代码

上面就是一个简单模拟require函数,也就是常用的以缓冲换效率的策略,当然实际情况要比上面复杂的多。比如是如何搜索的,如何预加载的等等。