博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
270行代码实现一个AMD模块加载器
阅读量:5965 次
发布时间:2019-06-19

本文共 10489 字,大约阅读时间需要 34 分钟。

AMD模块加载器

模块加载对于前端来说是非常重要的一个知识点.当前的主流模块加载方式有AMDCMDCommonJsnode环境下)。AMDCMD有很多共同之处例如都是文件提前并行载入,依赖载入完成通知模块,模块监听依赖是否全部执行完毕,如果直接执行factory.核心原理区别其实并不大,只不过代码的执行顺序有所区别而已。AMD提前执行CMD按需执行.而CMDCommonJs的区别在于文件的载入顺序不同,CommonJS文件是就近载入,代码就近执行.这里我用最少的代码来实现一个AMD模块加载器以便大家来真正理解掌握前端的模块加载

fw.js

不废话,直接上代码。重要的代码块我已写上注释

var fwjs, require, define;(function (global) {    var req,         ob = Object.prototype,        toString = ob.toString,        hasOwn = ob.hasOwnProperty,        version = "1.0.0",        contexts = {},        head = document.getElementsByTagName('head')[0],        globalDefQueue = [],        defContextName ="_";    function isFunction(f) {        return toString.call(f) == "[object Function]"    }    function isArray(arr) {        return toString.call(arr) == "[object Array]"    }    function getOwn(obj, prop) {        return hasOwn.call(obj, prop) && obj[prop];    }    // 创建script节点    function createScriptNode(){        var node =  document.createElement('script');        node.type = 'text/javascript';        node.charset = 'utf-8';        node.async = true;        return node;    };    // 载入js文件    function loadScript(fn, moduleName, url){        var node = createScriptNode();        node.setAttribute('data-fwmodule', moduleName);        node.addEventListener('load', fn, false);        node.src = url;        head.appendChild(node)    }    function createContext(){        var context = {},            registry = {},            undefEvents = {},            defined = {},            urlLoaded = {},            defQueue=[],            requireCounter = 1        ;        function makeModuleMap(name, parentModuleMap){            var isDefine = true,                normalizedName = "",                originalName = name,                url = name,                parentName = parentModuleMap ? parentModuleMap.name : "";            if (!name) {                    isDefine = false;                    name = 'fw' + (requireCounter += 1);            }            // 在这里并没有对id和name进行处理 主要是不支持config            return {                name: name,                parentMap: parentModuleMap,                url: name,                originalName: originalName,                isDefine: isDefine,                id: name            };        }        function getModule(depMap) {            var id = depMap.id,                mod = getOwn(registry, id);            if (!mod) {                mod = registry[id] = new context.Module(depMap);            }            return mod;        }        // 构建Module类        function Module(map){            this.events = getOwn(undefEvents, map.id) || {};            this.map = map;            this.depExports = [];            this.depMaps = [];            this.depMatched = [];  // 依赖是否已defined            this.depCount = 0;        }        Module.prototype = {            // 模块初始化            init: function(depMaps, factory){                if (this.inited) {                    return;                }                this.factory = factory;                this.inited = true;                this.depMaps = depMaps || [];                this.enable();            },            // 启用模块            enable:function(){                this.enabled = true;                this.enabling = true;                this.depMaps.forEach(function(depMap, i) {                    var mode = null;                    if (typeof depMap == "string") {                        depMap = makeModuleMap(depMap, this.map.isDefine ? this.map : null);                        mod = getOwn(registry, depMap.id);                                                this.depCount += 1;                        this.depMaps[i] = depMap;                        var fn = function (depExports) {                            if (!this.depMatched[i]) {                                this.depMatched[i] = true;                                this.depCount -= 1;                                this.depExports[i] = depExports;                            }                            this.check();                        }.bind(this)                        // 如果模块已经加载过                        if (getOwn(defined, depMap.id) && mod.defineEmitComplete) {                            fn(defined[depMap.id]);                        } else {                            mod = getModule(depMap);                            // 绑定defined事件,监听依赖的载入,每一个依赖载入完成,模块都会收集依赖的exports,但所有的依赖载入完毕模块才会运行                             mod.on("defined", fn)                          }                        mod = registry[depMap.id];                        if (mod && !mod.enabled) {                            mod.enable()                        }                    }                }.bind(this))                this.enabling = false;                this.check();            },            // 执行模块            check:function(){                if (!this.enabled || this.enabling) {                    return;                }                var id = this.map.id,                    depExports = this.depExports,                    exports = this.exports,                    factory = this.factory;                if (!this.inited) {                    this.load(); //                  } else if (!this.defining){                    this.defining = true;                               // defining下面代码每个模块只执行一次                    if (isFunction(factory)) {                          // 模块的factory只允许是函数                                                                        // 只有模块的依赖全部执行完,才会运行factory                        if (this.depCount < 1 && !this.defined) {       // 只有暴露出exports defined属性才为true                            exports = factory.apply(this, depExports)                            this.exports = exports;                            if (this.map.isDefine) {                                defined[id] = exports;                            }                            this.defined = true;                         }                      }                           this.defining = false;                    if (this.defined && !this.defineEmitted) {                        this.defineEmitted = true;                                                 this.emit('defined', this.exports);                        this.defineEmitComplete = true;                    }                }                },            load(){                if (this.loaded) {                    return;                }                this.loaded = true;                var url = this.map.url;                //Regular dependency.                if (!urlLoaded[url]) {                           urlLoaded[url] = true;                    loadScript(context.onScriptLoad, this.map.id, url)                 }            },            on: function (name, cb) {                var cbs = this.events[name];                if (!cbs) {                    cbs = this.events[name] = [];                }                cbs.push(cb);            },            emit: function(name, data){                var evts = this.events[name] || [];                evts.forEach(function(cb){                    cb(data);                })            }        }        // 将globalQueue转入defQueue        function getGlobalQueue() {            //Push all the globalDefQueue items into the context's defQueue            if (globalDefQueue.length) {                globalDefQueue.forEach(function(queueItem) {                    var id = queueItem[0];                    if (typeof id === 'string') {                        context.defQueueMap[id] = true;                    }                    defQueue.push(queueItem);                });                globalDefQueue = [];            }        }        context.Module = Module;        context.require = function (deps, callback){            //console.log(deps, callback)            var requireMod = getModule(makeModuleMap(null));            requireMod.init(deps, callback);        };        context.onScriptLoad = function(evt){            if (evt.type == "load") {                var node = evt.currentTarget || evt.srcElement;                node.removeEventListener('load', context.onScriptLoad, false);                var id = node.getAttribute('data-fwmodule')                context.completeLoad(id);            }        };        context.completeLoad = function(moduleName){            var found, args;            // 提取当前载入的模块            getGlobalQueue();            while (defQueue.length) {                args = defQueue.shift();                if (args[0] === null) {                    args[0] = moduleName;                    if (found) {                        break;                    }                    found = true;                } else if (args[0] === moduleName) {                    found = true;                }                if (!getOwn(defined, args[0])) {                    // 依赖载入完成之后,对文件进行初始化                    getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);                }            }        }        return context;    }    // 入口的require函数    req = fwjs = require = function(deps, callback) {        var context = {},            contextName = defContextName;        if (isFunction(deps)) {            deps = [];            callback = deps        }        context = getOwn(contexts, contextName);        if (!context){            context = contexts[contextName] = createContext();        }        return context.require(deps, callback)    }    // define只允许匿名模块    define = function(deps, callback){        if (!isArray(deps)) {            callback = deps;            deps = [];        }        var name = null, context;        globalDefQueue.push([name, deps, callback]);        globalDefQueue[name] = true;    }}(this))复制代码

代码为什么会这么少?

  1. 不兼容IE
  2. 不支持config(不需要处理大量的复杂的pluigin, shim, baseUrl之类的全局配置)
  3. 只支持匿名模块(不需要处理name),模块只支持用函数包裹
  4. 仅供学习使用,以理解AMD的模块加载原理

实现原理

因为代码很少,大家看几遍就很容易看懂了,所以这个我只是简单的说下吧

每一个依赖加载运行完都让depCount减一并且模块会check,如果depCount为0就会运行factoryfactory运行完继续向上通知因为模块本身可以是依赖, 直至入口的require模块运行

demo

index.js

require( ["./src/app.js", "./src/app1.js", "./src/m/app2.js"],    function(a, b, c) {        console.log(a, b, c)    });复制代码

app.js

define(function () {    //Do setup work here    return {        name:"app"    }});复制代码

app1.js

define(function () {    //Do setup work here    return {        name: "app1"    }});复制代码

m/app2.js

define(["./src/app.js"],function (a) {    console.log(a)    //Do setup work here    return {        name:"app2"    }});复制代码

展示

RequireJs

我的代码是参照RequireJs编写的 用兴趣的可以直接阅读RequireJs的源代码

结束

这是地址,如果大家觉得写的好可以给我个

转载地址:http://wmjax.baihongyu.com/

你可能感兴趣的文章
日期和时间类函数
查看>>
观察者模式C#实现实例(二)
查看>>
mysql易混淆知识点
查看>>
MongoDB 数据迁移和同步
查看>>
nginx下yii2设置路由规则
查看>>
access表达式类型不匹配
查看>>
架构师入门:搭建基本的Eureka架构(从项目里抽取)
查看>>
hdu 2050 折线分割平面 (递推)
查看>>
HDU 2844 Coins 【多重背包*】
查看>>
SQL server 清除缓存
查看>>
Scrum立会报告+燃尽图(十月二十八日总第十九次)
查看>>
学习日记的写作原则
查看>>
对student进行增删改
查看>>
openstack namespace 的应用
查看>>
异常处理
查看>>
Hbase之取出行数据指定部分+版本控制(类似MySQL的Limit)
查看>>
Vue router 全局路由守卫
查看>>
Java控制语句——switch语句
查看>>
流与序列化
查看>>
虚拟机封装
查看>>