再探Lua的require

咱們以前實現了本身版本的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

若是你以爲個人文章有幫助,歡迎關注個人微信公衆號(大齡社畜的遊戲開發之路-

相關文章
相關標籤/搜索