无工具js直接在网页上实现模块化

29次阅读

共计 3427 个字符,预计需要花费 9 分钟才能阅读完成。

大概原理就是异步加载一堆脚本文件,个人项目可以试试,企业项目使用请深思熟虑后再进行尝试。
我了解到的方法来自于《javascript 设计模式》,作者是张容铭,因为觉得这个挺好玩的,给大家分享一下。


既然要直接在网页上实现模块化,肯定需要异步添加脚本文件,所以需要解决两个麻烦的 问题

  1. 依赖的模块也有依赖怎么办
  2. 如何知道异步添加的 js 文件加载的状态

//main.js 不用关注细节,快速了解下调用方式即可
m.module(['dom/index'], function (dom) {var test = document.getElementById('test')
    console.log(dom(test).attr('id'))
    dom(test).css({
        'background': '#000',
        'color': '#fff'
    })
})
......
//dom/css.js
m.module('dom/css', ['shared/util'], function (util) {return function (ele, css, value) {if (util.isString(css)) {if (util.isNumber(value)) {return ele.style.css = value}
            return ele.style.css
        } else {for (var k in css) {ele.style[k] = css[k]
            }
        }
    }
})
......
//shared/util.js
m.module('shared/util', function () {
    return {isNumber: function (num) {return num === (0 || num) && num.constructor === Number
        },
        isString: function (str) {return str === ("" || str) && str.constructor === String
        }
    }
})

下面就开始实现这个暴露出来的 module 函数

遵守规则

将模块的实现隐藏起来,创建一个闭包

(function(m){var m = m()
})(function(){window.m = {}
})

工具函数

添加两个工具函数,loadScript 和 getUrl

// 加载脚本
var loadScript = function (src) {var _script = document.createElement('script')
        _script.type = 'text/javascript'
        _script.async = true
        _script.src = src

        document.getElementsByTagName('head')[0].appendChild(_script)
    },
    // 为地址添加一个.js
    getUrl = function (moduleName) {return String(moduleName).replace(/\.js$/g, '') +'.js'
    }

module 函数的实现

通过上面的图片示例可以了解到,module 函数包括了创建和调用模块的功能,它拥有三个参数

  • url 地址
  • deps 数据类型为数组的依赖模块
  • callback 该模块的主函数

获取参数

m.module=function(){var args = [].slice.call(arguments),
        // 取最后一个参数, 即 callback
        callback = args.pop(),
        // 获取依赖,且数据类型为数组
        deps = (args.length && args[args.length - 1] instanceof Array) ? args.pop() : [],
        // 地址
        url = args.length ? args.pop() : null
...

这就是完整 module 函数的实现,初看很复杂,别急,module 的关键就在于下面的两个函数(loadModule 和 setModule),凡是异步原理都是在和大脑作对,习惯就是新世界的大门,不要拘泥于阅读顺序

m.module = function () {var args = [].slice.call(arguments),
        callback = args.pop(),
        deps = (args.length && args[args.length - 1] instanceof Array) ? args.pop() : [],
        url = args.length ? args.pop() : null
        
        params = [],// 依赖模块序列,主函数(回调函数)的使用的参数
        depsCount = 0,// 该模块未加载完毕的依赖数量
        i = 0
        
    if (deps.length) {while (i < deps.length) {
            // 闭包保存 i
            (function (i) {
                // 这样每个脚本执行的 module 都会有一个 depsCount
                depsCount++
                //loadModule 初始化是不会调用它的回调函数(缓冲器)的(可以先翻到下面看 loadModule 的实现)
                // 但它会把回调函数添加到 moduleCache 中去, 同时加载该依赖的脚本
                loadModule(deps[i], function (mod) {
                    // 这里的 mod 是依赖模块的输出
                    params[i] = mod
                    // 等于 0 的时候就会执行自己的回调
                    depsCount--  
                    if (depsCount === 0) {
                        // 将依赖模块的输出添加到 callback 的参数中,这样主函数就可以直接使用参数进行调用
                        setModule(url, params, callback)
                    }
                })
            })(i)
            i++
        }
    } else {// 一旦依赖走到底部,也就是一个脚本文件里的模块没有了依赖(可以先看看下面 setModule)
        //loadModule 初始化添加到 moduleCache 的回调就会执行,而 depsCount 就会 -1
        setModule(url, [], callback)
    }
}

如果没有依赖的话,会直接执行 setModule,该模块如果是被依赖的模块,就会调用 loadModule 缓存的缓冲器,也就是它的回调函数
可以先看看 loadModule 和 setModule 的实现

if(deps.length){...}else{setModule(url, [], callback)
}

添加一个 moduleCache 变量,用于缓存模块

// 闭包内部
var moduleCache = {}
var setModule = function (moduleName, params, callback) {
    var _module, fn
    if (moduleCache[moduleName]) {_module = moduleCache[moduleName]
        _module.status = 'loaded'
        //export 是模块的输出
        _module.exports = callback ? callback.apply(_module, params) : null
        while (fn = _module.onload.shift()) {
            // 执行回调,并将自己的模块输出到缓冲器中
            fn(_module.exports)
        }
    } else {callback && callback.apply(null, params)
    }
}
// 这里参数 callback 不是主函数,而是保存的缓冲器,详细翻回 module 的完整函数
var loadModule = function (moduleName, callback) {
    var _module
    // 已初始化
    if (moduleCache[moduleName]) {_module = moduleCache[moduleName]
        if (_module.status === 'loaded') {
            // 有就直接从 moduleCache 缓存中获取
            setTimeout(callback(_module.exports), 4)
        } else {_module.onload.push(callback)
        }
    } else {
        // 初始化
        moduleCache[moduleName] = {
            // 地址,也可以叫模块名称
            moduleName: moduleName,
            status: 'loading',
            // 该模块 return 的输出
            exports: null,
            onload: [callback]
        }
        // 添加脚本
        loadScript(getUrl(moduleName))
    }
}

第一次写文章,如果觉得不好理解或者有行文不严谨的地方可以发下评论或者私信我修改
有帮助的话给我个赞哦

正文完
 0