咱們以前實現了本身版本的require,首先認定爲lua模塊嘗試加載,若是加載不成功則認定爲C模塊繼續進行加載。實際上,在Lua內部,是經過searchers來區分不一樣的加載方式。Lua一共有4種searchers,用來加載lua模塊的和加載C模塊的分別是第2個和第3個。第1個searcher叫作preload,它使用package.preload
這個內部table,根據加載模塊的名稱,去找到對應的加載函數來加載模塊,例如:微信
-- mypackage.lua print("hello world mypackage")
咱們執行:函數
print(next(package.preload)) package.preload["mypackage"] = function(module) print("module ", module) end require("mypackage") require("mypackage")
輸出以下:ui
能夠看到,默認狀況下package.preload
是一個空的table,咱們爲mypackge模塊指定一個自定義的加載函數後,調用require就會調用咱們自定義的函數了。一樣地,若是這個函數沒有返回值,lua會爲之添加一個true
的返回值,標記該模塊已經加載過,無需重複加載。lua
咱們還能夠經過如下代碼,來確認lua的第1個searcher爲preload:設計
package.preload["mypackage"] = function(module) print("module ", module) end package.searchers[1] = function(module) print("searchers 1 ", module) return function(module) print("my loader") end end package.searchers[2] = nil require("mypackage")
首先,package.searchers
中的每個searcher都要返回一個loader函數表明加載成功,不然即爲加載失敗,lua會依次在這個list中繼續調用下一個searcher,直到返回一個loader函數。所以,這裏咱們顯式地把package.searchers
的長度縮減爲1,避免lua調用其餘searcher影響咱們對結果的判斷。輸出以下:code
能夠發現,package.preload
中的函數並無被調用,調用的其實是咱們重寫過的package.searchers[1]
。這就說明了第1個searcher即爲preload。blog
一樣地,咱們能夠用相似的手段,去發現第2個searcher是返回加載lua模塊的loader,第3個searcher是返回加載C模塊的loader:遊戲
package.searchers[2] = function(module) return function(module) print("my loader") end end require("mypackage")
package.searchers[3] = function(module) return function(module) print("my loader") end end -- WinFeature.dll require("WinFeature")
有時,咱們須要在一個模塊中定義子模塊,lua自己也支持了加載子模塊的機制。模塊的層級用.
號來區分。例如咱們嘗試加載一個子模塊:ip
require("a.b")
輸出結果以下:遊戲開發
能夠看到,若是是lua模塊,那麼子模塊至關於父模塊的子目錄下。若是是C++模塊,那麼狀況有些特殊,子模塊除了可能存在父模塊的子目錄下,也有可能就和父模塊在同一個dll中。負責搜索和父模塊在同一個dll的searcher就是第4個searcher。相似咱們也能夠作出以下驗證:
package.searchers[4] = nil require("a.b")
執行,此時輸出結果以下:
對比一下,便可獲得答案。第4個searcher就是專門用來搜索包含多個子模塊的dll的。該dll須要暴露給lua的導出函數名爲luaopen_a_b
。
而後,讓咱們回到模塊自己中來。通常,咱們是這樣編寫一個模塊的:
-- mypackage.lua local M = {} local privateField = 1 M.publicField = 1 local privateFunc = function() print("i am a private function") end M.publicFunc = function() print("i am a public function") end return M
local定義的都只在本模塊中可見,能夠認爲是私有的成員和函數。咱們執行:
m = require("mypackage") print(m.privateField) print(m.publicField) m:privateFunc() m:publicFunc()
不過,若是咱們在某個模塊中定義了全局變量,在require以後,也會引入到調用require的環境中,這每每是咱們不想要的:
-- mypackage.lua local M = {} globalField = 1 globalFunc = function() print("i am a global function") end return M
咱們以前在編寫本身的require時提過,能夠在loadFile
函數傳入一個環境參數,來禁止模塊定義全局變量,這裏咱們能夠靈活修改一下,讓模塊中的全局變量變成只屬於該模塊自身的一個變量。
最後,讓咱們根據lua的設計,從新實現一下require:
local env = {} local searchers = {} searchers[1] = function(module) local loader = package.preload[module] return loader end searchers[2] = function(module) module = string.gsub(module, '%.', '/') for pattern in string.gmatch(package.path, '[^;]+%?[^;]+') do local path = string.gsub(pattern, '%?', module) setmetatable(env, {__index = _G, __newindex = function(t, k, v) rawset(t, k, v) end}) local loader = loadfile(path, nil, env) if loader then return loader end end end searchers[3] = function(module) module = string.gsub(module, '%.', '/') for pattern in string.gmatch(package.cpath, '[^;]+%?[^;]+') do local path = string.gsub(pattern, '%?', module) local loader = package.loadlib(path, "luaopen_" .. module) if loader then return loader end end end searchers[4] = function(module) local dotIndex = string.find(module, '%.') if not dotIndex then return end local lib = string.sub(module, 1, dotIndex - 1) module = string.gsub(module, '%.', '_') for pattern in string.gmatch(package.cpath, '[^;]+%?[^;]+') do local path = string.gsub(pattern, '%?', lib) local loader = package.loadlib(path, "luaopen_" .. module) if loader then return loader end end end function require_ex(module) if package.loaded[module] then return package.loaded[module] end env = {} for _, searcher in ipairs(searchers) do local loader = searcher(module) if loader and type(loader) == "function" then local ret = loader(module) if type(ret) ~= "table" then package.loaded[module] = env else for k, v in pairs(env) do if ret[k] then print("set multiple value") else ret[k] = v end end package.loaded[module] = ret end return package.loaded[module] end end end
若是你以爲個人文章有幫助,歡迎關注個人微信公衆號(大齡社畜的遊戲開發之路)-