环境: html
Lua5.1 LuaForWindowswindows
LuaForWindows的下载地址ide
http://files.luaforge.net/releases/luaforwindows/luaforwindowsui
正题:lua
require做用相似于C/C++中的#include,其特性为:spa
1. 根据搜索目录加载指定模块.net
2. 断定模块是否已加载,避免重复加载debug
其实现原理:设计
对于require加载的模块数据是存储在package.loaded表中,其存储方式以模块名为key,以返回值(模块若无返回值,默认为true)为value进行存储的。好比运行以下程序:code
-- package.loaded的类型 print(type(package.loaded)) -- table -- 没有require模块文件 for i, v in pairs(package.loaded) do print(i,v) end --[[ >>>输出 string table: 006EDE40 debug table: 006EDF08 package table: 006EDAF8 _G table: 00501C88 io table: 006EDC88 os table: 006EDDC8 table table: 006EDB98 math table: 006EDEB8 ]]
而后咱们任意加载一个Demo.lua,代码以下:
-- requireDemo.lua -- 内部实现代码: local requireDemo = {} return true
require("requireDemo") for i, v in pairs(package.loaded) do print(i,v) end --[[ 输出: string table: 002DDE40 debug table: 002DDF08 package table: 002DDAF8 _G table: 008C1C88 io table: 002DDC88 os table: 002DDDC8 table table: 002DDB98 math table: 002DDEB8 coroutine table: 002DDA58 requireDemo true -- 新加的,已经加入到表中了 ]]
通过如上代码对比,咱们能够这样理解,经过require加载某模块的时候,首先经过package.loaded来断定是否已加载。若是已加载,则返回该模块数据,若是没有加载,进行搜索加载,若是加载成功,返回该模块数据,不然报错。好比:
-- 加载不存在的模块 require("ErrorModel") --[[ 错误堆栈信息: lua: require.lua:21: module 'ErrorModel' not found: no field package.preload['ErrorModel'] no file '.\ErrorModel.lua' no file 'E:\Program Files (x86)\Lua\5.1\lua\ErrorModel.lua' no file 'E:\Program Files (x86)\Lua\5.1\lua\ErrorModel\init.lua' no file 'E:\Program Files (x86)\Lua\5.1\ErrorModel.lua' no file 'E:\Program Files (x86)\Lua\5.1\ErrorModel\init.lua' no file 'E:\Program Files (x86)\Lua\5.1\lua\ErrorModel.luac' no file '.\ErrorModel.dll' no file '.\ErrorModel51.dll' no file 'E:\Program Files (x86)\Lua\5.1\ErrorModel.dll' no file 'E:\Program Files (x86)\Lua\5.1\ErrorModel51.dll' no file 'E:\Program Files (x86)\Lua\5.1\clibs\ErrorModel.dll' no file 'E:\Program Files (x86)\Lua\5.1\clibs\ErrorModel51.dll' no file 'E:\Program Files (x86)\Lua\5.1\loadall.dll' no file 'E:\Program Files (x86)\Lua\5.1\clibs\loadall.dll' stack traceback: [C]: in function 'require' require.lua:21: in main chunk [C]: ? ]]
对比着错误的堆栈信息,咱们能够获得这样的信息:根据指定的搜索路径,查找了Lua和C中的相关文件。咱们来详细的说明下:
在程序中,lua是经过LUA_PATH进行初始化,C是经过LUA_CPATH进行初始化的
// 在luaconf.h中 // Environment variable names for path overrides and initialization code // 用于初始化和覆盖环境变量名 #define LUA_PATH "LUA_PATH" #define LUA_CPATH "LUA_CPATH"
若是LUA_PATH,LUA_CPATH没有相关的变量,会经过LUA_PATH_DEFAULT,LUA_CPATH_DEFAULT来进行默认初始化,代码以下:
// luaconf.h中 /* ** In Windows, any exclamation mark ('!') in the path is replaced by the ** path of the directory of the executable file of the current process. */ #define LUA_LDIR "!\\lua\\" #define LUA_CDIR "!\\" #define LUA_PATH_DEFAULT \ ".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" #define LUA_CPATH_DEFAULT \ ".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll" #else /* ** Note to distribution maintainers: do NOT patch the following lines! ** Please read ../doc/install.html#distro and pass PREFIX=/usr instead. */ #ifndef LUA_MULTILIB #define LUA_MULTILIB "lib" #endif #ifndef LUA_LMULTILIB #define LUA_LMULTILIB "lib" #endif #define LUA_LROOT "/usr/local" #define LUA_LUADIR "/lua/5.1/" #define LUA_LJDIR "/luajit-2.1.0-beta2/" #ifdef LUA_ROOT #define LUA_JROOT LUA_ROOT #define LUA_RLDIR LUA_ROOT "/share" LUA_LUADIR #define LUA_RCDIR LUA_ROOT "/" LUA_MULTILIB LUA_LUADIR #define LUA_RLPATH ";" LUA_RLDIR "?.lua;" LUA_RLDIR "?/init.lua" #define LUA_RCPATH ";" LUA_RCDIR "?.so" #else #define LUA_JROOT LUA_LROOT #define LUA_RLPATH #define LUA_RCPATH #endif #define LUA_JPATH ";" LUA_JROOT "/share" LUA_LJDIR "?.lua" #define LUA_LLDIR LUA_LROOT "/share" LUA_LUADIR #define LUA_LCDIR LUA_LROOT "/" LUA_LMULTILIB LUA_LUADIR #define LUA_LLPATH ";" LUA_LLDIR "?.lua;" LUA_LLDIR "?/init.lua" #define LUA_LCPATH1 ";" LUA_LCDIR "?.so" #define LUA_LCPATH2 ";" LUA_LCDIR "loadall.so" #define LUA_PATH_DEFAULT "./?.lua" LUA_JPATH LUA_LLPATH LUA_RLPATH #define LUA_CPATH_DEFAULT "./?.so" LUA_LCPATH1 LUA_RCPATH LUA_LCPATH2 #endif
初始化后,会将相关的路径分别放置到package.path,package.cpath中:
print("package.path路径相关:") print(package.path) --[[ -- 为了便于查看,进行了分行 ; .\?.lua; E:\Program Files (x86)\Lua\5.1\lua\?.lua; E:\Program Files (x86)\Lua\5.1\lua\?\init.lua; E:\Program Files (x86)\Lua\5.1\?.lua; E:\Program Files (x86)\Lua\5.1\?\init.lua; E:\Program Files (x86)\Lua\5.1\lua\?.luac ]] print("package.cpath路径相关:") print(package.cpath) --[[ -- 为了便于查看,进行了分行 .\?.dll; .\?51.dll; E:\Program Files (x86)\Lua\5.1\?.dll; E:\Program Files (x86)\Lua\5.1\?51.dll; E:\Program Files (x86)\Lua\5.1\clibs\?.dll; E:\Program Files (x86)\Lua\5.1\clibs\?51.dll; E:\Program Files (x86)\Lua\5.1\loadall.dll; E:\Program Files (x86)\Lua\5.1\clibs\loadall.dll ]]
看到如上的输出,其模块名的显示"?",在lua中,其搜索的路径实质上属于模板路径,在搜索模块时,首先会把模块名替换模块路径下的"?",再进行开始搜索。
从实质上来讲,搜索的步骤依次分为以下部分:
1. 预加载搜索,经过package.preload来进行
2. Lua中搜索,经过package.path获取搜索路径,成功后会调用loadFile加载
3. C库中搜索,经过package.cpath获取搜索路径,成功后,会调用loadlib来加载
加载成功后,会存储到表package.loaded中。
其它:可否从新加载模块吗?
答案能够,咱们须要将已加载模块的表置空,以下:
package.loaded["*"] = nil -- 置空已加载的模块数据 required("*") -- 再次加载
关于require的实现,代码以下:
-- 参考: Lua程序设计(第2版)
function require(name) -- 断定模块是否已加载 if not package.loaded[name] then local loader = findloader(name) if loader == nil then error("unable to load module " .. name) end -- 将模块标记为已加载 package.loaded[name] = true -- 初始化模块 local res = loader(name) if res ~= nil then package.loaded[name] = res end end -- 返回模块数据 return package.loaded[name] end