AMD加载器实现笔记(五)

  前几篇文章对AMD规范中的config属性几乎全部支持了,这一节主要是进一步完善。到目前为止我们的加载器还无法处理环形依赖的问题,这一节就是解决环形依赖。

  所谓环形依赖,指的是模块A的所有依赖项的依赖中有没有依赖A模块本身的模块。如果有那就说明存在环形依赖。所以检验的方式是利用递归,检查一个模块的依赖的依赖项中有没有依赖A模块,以及依赖项的依赖项的依赖项中有没有A模块,核心代码如下:

function checkCircleRef(start, target){
        var m = modules[start];
        if (!m) {
            return false;
        }
        var depModules = m.deps.map(function(dep) {
            return modules[dep] || null;
        });

        return depModules.some(function(m) {//检查依赖项的依赖项
            if (!m) {
                return false;
            }
            return m.deps.some(function(dep) {
                var equal = dep === target;
                if (equal) {
                    console.error("circle reference: ", target, m.id);
                }

                return equal;
            });
        }) ? true : depModules.some(function(m) {//检查依赖项的依赖项的依赖项
            if (!m) {
                return false;
            }
            return m.deps.some(function(dep) {
                return checkCircleRef(dep, target);
            });
        });
    };

  

  剩下的问题是我们把检查放到哪里去?我们的模块最先在require中注册,所以最佳的位置是放在require函数中去检查:

//require 函数
//。。。。。。。
var module = {
            id: id,
            deps: deps,
            factory: callback,
            state: 1,
            result: null
        };
        modules[id] = module;

        if (checkCircleRef(id, id)) {
            hasCircleReferece = true;
            return;
        }
// ......................
//.......................
//......................

  下一个要面临的问题就是:如果存在依赖项如何处理?对此,我的做法是如果存在环形依赖,结束整个加载过程。我们在加载器内部使用一个哨兵变量,一旦存在环形依赖,停止所有工作。如:loadJs中:

script.onload = function() {
            if (hasCircleReferece) {
                return;
            }
            var module = modules[url];
            if (module && isReady(module) && loadings.indexOf(url) > -1) {
                callFactory(module);
            }
            checkDeps();
        };

  如define函数中:

if (modules[id]) {
            console.error(‘multiple define module: ‘ + id);
        }

        if (!hasCircleReferece) {
            require(deps, callback, id);
        }

  测试:

require.config({
    baseUrl: "./",
    packages: [{
        name: "more",
        location: "./more"
    }, {
        name: "mass",
        location: "../"
    }, {
        name: "wab",
        location: "../../../"
    }],
    shim: {
        "something": {
            "deps": [‘jquery‘],
            exports: ‘something‘,
            init: function(jq, ol) {
                console.log(jq);
                console.log($);
                return something + " in shim";
            }
        }
    },
    map: {
        ‘*‘: {
            ‘jquery‘: ‘jquery-private‘
        },
        ‘jquery-private‘: {
            ‘jquery‘: ‘jquery‘
        }
    },
    paths: {
        ‘jquery‘: "../../Bodhi/src/roots/jquery"
    }
  });
  require([
  ‘bbb‘,
  ‘aaa.bbb.ccc‘,
  ‘ccc‘,
  ‘ddd‘,
  ‘fff‘,
  ‘something‘
  ], function(aaabbbccc){
    console.log(‘simple loader‘);
    console.log(arguments);
  });

  bbb中:

define(["aaa",
"ccc"],function(a, c){
    console.log("已加载bbb模块", 7)
    return {
        aaa: a,
        ccc: c.ccc,
        bbb: "bbb"
    }
})

  aaa中:

define([‘bbb‘], function(){
    console.log("已加载aaa模块", 7)
    return "aaa"
});

  测试结果:

circle reference:  simpleAMDLoader/aaa simpleAMDLoader/bbb

  目前为止整个加载器代码如下:

  1 (function(global){
  2     global = global || window;
  3     var modules = {};
  4     var loadings = [];
  5     var loadedJs = [];
  6     var hasCircleReferece = false;
  7     //module: id, state, factory, result, deps;
  8     global.require = function(deps, callback, parent){
  9         var id = parent || "Bodhi" + Date.now();
 10         var cn = 0, dn = deps.length;
 11         var args = [];
 12
 13         var oriDeps = deps.slice();//保留原始dep的模块Id
 14
 15          // dep为非绝对路径形式,而modules的key仍然需要绝对路径
 16         deps = deps.map(function(dep) {
 17             if (modules[dep]) { //jquery
 18                 return dep;
 19             } else if (dep in global.require.parsedConfig.paths) {
 20                 return dep;
 21             }
 22             var rel = "";
 23             if (/^Bodhi/.test(id)) {
 24                 rel = global.require.parsedConfig.baseUrl;
 25             } else {
 26                 var parts = parent.split(‘/‘);
 27                 parts.pop();
 28                 rel = parts.join(‘/‘);
 29             }
 30             return getModuleUrl(dep, rel);
 31         });
 32
 33         var module = {
 34             id: id,
 35             deps: deps,
 36             factory: callback,
 37             state: 1,
 38             result: null
 39         };
 40         modules[id] = module;
 41
 42         if (checkCircleRef(id, id)) {
 43             hasCircleReferece = true;
 44             return;
 45         }
 46         //checkCircleRef(id, id)
 47
 48         deps.forEach(function(dep, i) {
 49             if (modules[dep] && modules[dep].state === 2) {
 50                 cn++
 51                 args.push(modules[dep].result);
 52             } else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {
 53                 loadJS(dep, oriDeps[i]);
 54                 loadedJs.push(dep);
 55             }
 56         });
 57         if (cn === dn) {
 58             callFactory(module);
 59         } else {
 60             loadings.push(id);
 61             checkDeps();
 62         }
 63     };
 64
 65     global.require.config = function(config) {
 66         this.parsedConfig = {};
 67         if (config.baseUrl) {
 68             var currentUrl = getCurrentScript();
 69             var parts = currentUrl.split(‘/‘);
 70             parts.pop();
 71             var currentDir = parts.join(‘/‘);
 72             this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
 73         }
 74         var burl = this.parsedConfig.baseUrl;
 75         // 得到baseUrl后,location相对baseUrl定位
 76         this.parsedConfig.packages = [];
 77         if (config.packages) {
 78             for (var i = 0, len = config.packages.length; i < len; i++) {
 79                 var pck = config.packages[i];
 80                 var cp = {
 81                     name: pck.name,
 82                     location: getRoute(burl, pck.location)
 83                 }
 84                 this.parsedConfig.packages.push(cp);
 85             }
 86         }
 87
 88
 89         this.parsedConfig.paths = {};
 90         if (config.paths) {
 91             for (var p in config.paths) {
 92                 this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);
 93             }
 94         }
 95
 96         this.parsedConfig.map = {};
 97         if (config.map) {
 98             this.parsedConfig.map = config.map;
 99         }
100
101         this.parsedConfig.shim = {};
102         //shim 要放在最后处理
103         if (config.shim) {
104             this.parsedConfig.shim = config.shim;
105             for (var p in config.shim) {
106                 var item = config.shim[p];
107                 define(p, item.deps, function() {
108                     var exports;
109                     if (item.init) {
110                         exports = item.init.apply(item, arguments);
111                     }
112
113                     return exports ? exports : item.exports;
114                 });
115             }
116         }
117
118         console.log(this.parsedConfig);
119     }
120
121     global.define = function(id, deps, callback) {
122         //加上moduleId的支持
123         if (typeof id !== "string" && arguments.length === 2) {
124             callback = deps;
125             deps = id;
126             id = "";
127         }
128         var id = id || getCurrentScript();
129
130         var mId = getModuleId(id);
131         if (mId || id in require.parsedConfig.shim) {
132             mId = mId ? mId : id;
133             var maping = getMapSetting(mId);
134
135             if (maping) {
136                 deps = deps.map(function(dep) {
137                     return maping[dep] || dep;
138                 });
139             }
140         }
141         if (modules[id]) {
142             console.error(‘multiple define module: ‘ + id);
143         }
144
145         if (!hasCircleReferece) {
146             require(deps, callback, id);
147         }
148     };
149
150     global.define.amd = {};//AMD规范
151
152     function getModuleId(url) {
153         var script = document.querySelector(‘script[src="‘ + url + ‘"]‘);
154         if (script) {
155             return script.getAttribute(‘data-moduleId‘);
156         } else {
157             return null;
158         }
159     };
160
161     function getMapSetting(mId) {
162         if (mId in require.parsedConfig.map) {
163             return require.parsedConfig[mId];
164         } else if (‘*‘ in require.parsedConfig.map) {
165             return require.parsedConfig.map[‘*‘];
166         } else {
167             return null;
168         }
169     };
170
171     function checkCircleRef(start, target){
172         var m = modules[start];
173         if (!m) {
174             return false;
175         }
176         var depModules = m.deps.map(function(dep) {
177             return modules[dep] || null;
178         });
179
180
181         return depModules.some(function(m) {
182             if (!m) {
183                 return false;
184             }
185             return m.deps.some(function(dep) {
186                 var equal = dep === target;
187                 if (equal) {
188                     console.error("circle reference: ", target, m.id);
189                 }
190
191                 return equal;
192             });
193         }) ? true : depModules.some(function(m) {
194             if (!m) {
195                 return false;
196             }
197             return m.deps.some(function(dep) {
198                 return checkCircleRef(dep, target);
199             });
200         });
201     };
202
203     function getRoute(base, target) {
204         var bts = base.replace(/\/$/, "").split(‘/‘);  //base dir
205         var tts = target.split(‘/‘); //target parts
206         while (isDefined(tts[0])) {
207             if (tts[0] === ‘.‘) {
208                 return bts.join(‘/‘) + ‘/‘ + tts.slice(1).join(‘/‘);
209             } else if (tts[0] === ‘..‘) {
210                 bts.pop();
211                 tts.shift();
212             } else {
213                 return bts.join(‘/‘) + ‘/‘ + tts.join(‘/‘);
214             }
215         }
216     };
217
218     function isDefined(v) {
219         return v !== null && v !== undefined;
220     };
221
222     function getModuleUrl(moduleId, relative) {
223         function getPackage(nm) {
224             for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
225                 var pck = require.parsedConfig.packages[i];
226                 if (nm === pck.name) {
227                     return pck;
228                 }
229             }
230             return false;
231         }
232         var mts = moduleId.split(‘/‘);
233         var pck = getPackage(mts[0]);
234         if (pck) {
235             mts.shift();
236             return getRoute(pck.location, mts.join(‘/‘));
237         } else if (mts[0] === ‘.‘ || mts[0] === ‘..‘) {
238             return getRoute(relative, moduleId);
239         } else {
240             return getRoute(require.parsedConfig.baseUrl, moduleId);
241         }
242     };
243
244     function loadJS(url, mId) {
245         var script = document.createElement(‘script‘);
246         script.setAttribute(‘data-moduleId‘, mId); //为script元素保留原始模块Id
247         script.type = "text/javascript";
248         //判断模块是否在paths中定义了路径
249         script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + ‘.js‘;
250         script.onload = function() {
251             if (hasCircleReferece) {
252                 return;
253             }
254             var module = modules[url];
255             if (module && isReady(module) && loadings.indexOf(url) > -1) {
256                 callFactory(module);
257             }
258             checkDeps();
259         };
260         var head = document.getElementsByTagName(‘head‘)[0];
261         head.appendChild(script);
262     };
263
264     function checkDeps() {
265         for (var p in modules) {
266             var module = modules[p];
267             if (isReady(module) && loadings.indexOf(module.id) > -1) {
268                 callFactory(module);
269                 checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功
270             }
271         }
272     };
273
274     function isReady(m) {
275         var deps = m.deps;
276         var allReady = deps.every(function(dep) {
277             return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;
278         })
279         if (deps.length === 0 || allReady) {
280             return true;
281         }
282     };
283
284     function callFactory(m) {
285         var args = [];
286         for (var i = 0, len = m.deps.length; i < len; i++) {
287             args.push(modules[m.deps[i]].result);
288         }
289         m.result = m.factory.apply(window, args);
290         m.state = 2;
291
292         var idx = loadings.indexOf(m.id);
293         if (idx > -1) {
294             loadings.splice(idx, 1);
295         }
296     };
297
298     function getCurrentScript(base) {
299         // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
300         var stack;
301         try {
302             a.b.c(); //强制报错,以便捕获e.stack
303         } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
304             stack = e.stack;
305             if (!stack && window.opera) {
306                 //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
307                 stack = (String(e).match(/of linked script \S+/g) || []).join(" ");
308             }
309         }
310         if (stack) {
311             /**e.stack最后一行在所有支持的浏览器大致如下:
312              *chrome23:
313              * at http://113.93.50.63/data.js:4:1
314              *firefox17:
315              *@http://113.93.50.63/query.js:4
316              *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
317              *@http://113.93.50.63/data.js:4
318              *IE10:
319              *  at Global code (http://113.93.50.63/data.js:4:1)
320              *  //firefox4+ 可以用document.currentScript
321              */
322             stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分
323             stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, ""); //去掉换行符
324             return stack.replace(/(:\d+)?:\d+$/i, "").replace(/\.js$/, ""); //去掉行号与或许存在的出错字符起始位置
325         }
326         var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找
327         for (var i = nodes.length, node; node = nodes[--i]; ) {
328             if ((base || node.className === moduleClass) && node.readyState === "interactive") {
329                 return node.className = node.src;
330             }
331         }
332     };
333 })(window)

时间: 2024-10-27 10:20:26

AMD加载器实现笔记(五)的相关文章

AMD加载器实现笔记(二)

AMD加载器实现笔记(一)中,我们实现了一个简易的模块加载器.但到目前为止这个加载器还并不能称为AMD加载器,原因很简单,我们还不支持AMD规范中的config配置.这篇文章中我们来添加对config的中baseUrl和packages的支持.API设计如下: 1 require.config({ 2 baseUrl: "./", 3 packages: [{ 4 name: "more", 5 location: "./more" 6 }, {

AMD加载器实现笔记(一)

之前研究过AMD,也写过一篇关于AMD的文章<以代码爱好者角度来看AMD与CMD>.代码我是有看过的,基本的原理也都明白,但实际动手去实现却是没有的.因为今年计划的dojo教程<静静的dojo>中,有一章节来专门讲解AMD,不免要把对AMD的研究回炉一下.时隔多日,再回头探索AMD实现原理时,竟抓耳挠腮,苦苦思索不得要领.作为开发人员,深感惭愧.故有此文,记录我在实现一个AMD加载器时的思考总结. requireJS是所有AMD加载器中,最广为人知的一个.目前的版本更凝聚了几位大牛

AMD加载器实现笔记(四)

继续这一系列的内容,到目前为止除了AMD规范中config的map.config参数外,我们已经全部支持其他属性了.这一篇文章中,我们来为增加对map的支持.同样问题,想要增加map的支持首先要知道map的语义. 主要用于解决在两个不同模块集中使用一个模块的不同版本,并且保证两个模块集的交互没有冲突. 假设磁盘有如下文件: 当'some/newmodule'请求'foo'模块时,它将从foo1.2.js总得到'foo1.2'模块:当'some/oldmodule'请求'foo'模块时它将从foo

AMD加载器实现笔记(三)

上一篇文章中我们为config添加了baseUrl和packages的支持,那么这篇文章中将会看到对shim与paths的支持. 要添加shim与paths,第一要务当然是了解他们的语义与用法.先来看shim,shim翻译成中文是“垫片”的意思.在AMD中主要用途是把不支持AMD的某些变量包装AMD模块.shim是一个哈希对象,key为包装后的模块Id,value是关于这个包装模块的一些配置,主要配置项如下: deps:定义模块需要的依赖项的moduleId数组 exports:模块输出值 in

JavaScript AMD 模块加载器原理与实现

关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者遵循 CMD规范.前者的规范产出比较适合于浏览器异步环境的习惯,后者的规范产出对于写过 nodejs 的同学来说是比较爽的.关于两者的比较,有兴趣的同学请参看玉伯在知乎的回答 AMD和CMD的区别有哪些.本文希望能按照 AMD 规范来简单实现自己的一个模块加载器,以此来搞清楚模块加载器的工作原理.

JS模块加载器加载原理是怎么样的?

路人一: 原理一:id即路径 原则.通常我们的入口是这样的: require( [ 'a', 'b' ], callback ) .这里的 'a'.'b' 都是 ModuleId.通过 id 和路径的对应原则,加载器才能知道需要加载的 js 的路径.在这个例子里,就是 baseUrl + 'a.js' 和 baseUrl + 'b.js'. 但 id 和 path 的对应关系并不是永远那么简单,比如在 AMD 规范里就可以通过配置 Paths 来给特定的 id 指配 path. 原理二:crea

模块加载器

模块加载器 最近在做新项目的时候自己利用一点业余时间写了一个简单的js模块加载器.后来因为用了webpack就没有考虑把它放到项目里面去,也没有继续更新它了.模块加载器开源的有很多,一般来说seaJS和reqiureJS都能满足基本需求.本篇博文主要分享一下卤煮写这个加载器的一些想法和思路,作为学习的记录. js模块化加载已经不是一个新鲜概念了,很多人都一再强调,大型项目要使用模块化开发,因为一旦随着项目的增大,管理和组织代码的难度会越来越难,使得我们对代码的管理变得重要起来.当然,在后端模块化

C编译器、链接器、加载器详解

摘自http://blog.csdn.net/zzxian/article/details/16820035 C编译器.链接器.加载器详解 一.概述 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接.编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程.链接是把目标文件.操作系统的启动代码和用到的库文件进行组织形成最终生成可加载.可执行代码的过程. 过程图解如下: 预处理器:将.c 文件转化成 .i文件,使用的gcc命令是

浏览器端的javascript加载器

commonJS,定义了一种同步加载脚本的规范.对于浏览器端而言,因为js脚本都是在远端,用同步的方式可能会长时间阻塞线程.因此,浏览器端的js加载器并不会严格按照commonJS来做.seajs作为一个试图遵循commonJS规范的加载器,是在规范的基础上在外面包一层define,来异步加载js,以下是seajs官网的一个例子: // 所有模块都通过 define 来定义 define(function(require, exports, module) { // 通过 require 引入依