• 再探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")
    

    输出如下:

    可以看到,默认情况下package.preload是一个空的table,我们为mypackge模块指定一个自定义的加载函数后,调用require就会调用我们自定义的函数了。同样地,如果这个函数没有返回值,lua会为之添加一个true的返回值,标记该模块已经加载过,无需重复加载。

    我们还可以通过以下代码,来确认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影响我们对结果的判断。输出如下:

    可以发现,package.preload中的函数并没有被调用,调用的实际上是我们重写过的package.searchers[1]。这就说明了第1个searcher即为preload。

    同样地,我们可以用类似的手段,去发现第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本身也支持了加载子模块的机制。模块的层级用.号来区分。例如我们尝试加载一个子模块:

    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
    

    如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路-

  • 相关阅读:
    Spark在MaxCompute的运行方式
    新功能初探 | MySQL 8.0 Multi-Valued Indexes功能简述
    吐血整理 | 1000行MySQL学习笔记,不怕你不会,就怕你不学!
    阿里巴巴架构师:十问业务中台和我的答案
    C# int?
    页面后退清空缓存
    oracle 中 创建序列sequence
    sql 与 oracle 几个简单语法差别
    oracle 中用法dual
    将DataTable进行分页并生成新的DataTable
  • 原文地址:https://www.cnblogs.com/back-to-the-past/p/14315863.html
Copyright © 2020-2023  润新知