zoukankan      html  css  js  c++  java
  • AMD加载器实现笔记(三)

      上一篇文章中我们为config添加了baseUrl和packages的支持,那么这篇文章中将会看到对shim与paths的支持。

      要添加shim与paths,第一要务当然是了解他们的语义与用法。先来看shim,shim翻译成中文是“垫片”的意思。在AMD中主要用途是把不支持AMD的某些变量包装AMD模块。shim是一个哈希对象,key为包装后的模块Id,value是关于这个包装模块的一些配置,主要配置项如下:

    • deps:定义模块需要的依赖项的moduleId数组
    • exports:模块输出值
    • init:如果它的返回值不是undefined,则返回值作为‘some/thind’模块的返回值,否则以exports作为模块的返回值。

      举个例子:

      

      这个配置的目的是想将window.some.thing这个全局变量包装成id为‘some/thing’的模块。模块的依赖项Id为'a'、'b',输出值为some.thing但因为定义了init函数,所以最后模块的输出值变成了some.thing + 'another'。

      因为shim是要将全局变量包装成模块,所以直接用shim中的配置项来定义模块即可。上例中的配置最终将转化为:

    1 define('some/thing', ['a', 'b'], function(a, b) {
    2    var initResult = shimItem.init(a, b);
    3    return initResult === undefined ? shimItem.exports : initResult;  
    4 });

      但前面我的加载器中,define只支持匿名模块,现在我们让它来支持显示定义模块Id:

    global.define = function(id, deps, callback) {
            //加上moduleId的支持
            if (typeof id !== "string" && arguments.length === 2) {
                callback = deps;
                deps = id;
                id = "";
            }
            var id = id || getCurrentScript();
            if (modules[id]) {
                console.error('multiple define module: ' + id);
            }
            
            require(deps, callback, id);
        };

      完成后我们在require.config函数中加入对shim的支持。

    //shim 要放在最后处理
            if (config.shim) {
                for (var p in config.shim) {
                    var item = config.shim[p];
                    define(p, item.deps, function() {
                        var exports;
                        if (item.init) {
                            exports = item.init.apply(item, arguments);
                        }
                        
                        return exports ? exports : item.exports;
                    });
                }
            }

      于此同时,require中也要改一下,现在已经不能一股脑的将dep都转化为绝对路径了。

     1 // dep为非绝对路径形式,而modules的key仍然需要绝对路径
     2         deps = deps.map(function(dep) {
     3             if (modules[dep]) { //jquery 
     4                 return dep;
     5             } else if (dep in global.require.parsedConfig.paths) {
     6                 return dep;
     7             }
     8             var rel = "";
     9             if (/^Bodhi/.test(id)) {
    10                 rel = global.require.parsedConfig.baseUrl;
    11             } else {
    12                 var parts = parent.split('/');
    13                 parts.pop();
    14                 rel = parts.join('/');
    15             }
    16             return getModuleUrl(dep, rel);
    17         });

      到此,shim已经完美支持了。

      下面轮到了paths,paths也是一个对象,格式为{模块Id:模块所在路径}。当其他模块引用该模块时,该模块的加载地址使用paths中所配置的地址,这个地址可以是绝对的,也可以是相对路径(相对于baseUrl)。首先要在require.config函数中解析path路径

    1 this.parsedConfig.paths = {};
    2         if (config.paths) {
    3             for (var p in config.paths) {
    4                 this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);
    5             }
    6         }

      然后当依赖模块Id在paths中有配置时,那就需要从paths中拿到加载地址,所以需要修改loadJs中代码

     1 function loadJS(url) {
     2         var script = document.createElement('script');
     3         script.type = "text/javascript";
     4         //判断模块是否在paths中定义了路径
     5         script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + '.js';
     6         script.onload = function() {
     7             var module = modules[url];
     8             if (module && isReady(module) && loadings.indexOf(url) > -1) {
     9                 callFactory(module);
    10             }
    11             checkDeps();
    12         };
    13         var head = document.getElementsByTagName('head')[0];
    14         head.appendChild(script);
    15     };

      同时如果dep在paths中有配置,也不能将dep转化为绝对路径(require函数)

      这里面需要注意的是,模块的Id必须与路径所在文件中定义的模块的id保持一致。以jquery为例:

     1 require.config({
     2     baseUrl: "./",
     3     packages: [{
     4         name: "more",
     5         location: "./more"
     6     }, {
     7         name: "mass",
     8         location: "../"
     9     }, {
    10         name: "wab",
    11         location: "../../../"
    12     }],
    13     paths: {
    14         'jquery': "../../Bodhi/src/roots/jquery"
    15     }
    16   });

      当在paths中配置了jquery模块时,那么路径对应的文件一定要定义jquery模块,如jquery源码中用define定义jquery模块:

    1 if ( typeof define === "function" && define.amd ) {
    2     define( "jquery", [], function() {
    3         return jQuery;
    4     });
    5 }

      

      本文内容结束,目前为止loader加载器的源码为:

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

      

      测试demo:

     1 window.something = "Bodhi";
     2   require.config({
     3     baseUrl: "./",
     4     packages: [{
     5         name: "more",
     6         location: "./more"
     7     }, {
     8         name: "mass",
     9         location: "../"
    10     }, {
    11         name: "wab",
    12         location: "../../../"
    13     }],
    14     shim: {
    15         "something": {
    16             "deps": ['jquery'],
    17             exports: 'something',
    18             init: function(jq, ol) {
    19                 console.log(jq);
    20                 return something + " in shim";
    21             }
    22         }
    23     },
    24     paths: {
    25         'jquery': "../../Bodhi/src/roots/jquery"
    26     }
    27   });
    28   require([
    29   'bbb',
    30   'aaa.bbb.ccc',
    31   'ccc',
    32   'ddd',
    33   'fff',
    34   'something'
    35   ], function(aaabbbccc){
    36     console.log('simple loader');
    37     console.log(arguments);
    38   });
  • 相关阅读:
    自定义协议的注册及程序示例转载
    在windows phone 中使用原生代码开发程序(native code)
    C# 操作系统防火墙转载
    WP7Windows Phone的Isolated Storage Explorer使用指南
    如何让wp7真机调试时候保持屏幕高亮不锁屏
    利用C#检测证书是否存在,并安装证书
    WPF设置附加属性的值
    wpf值转换器IValueConverter转载
    ADO.NET一些内幕
    怎样将数据库中所有表中含有numeric(18,2)字段改成numeric(18,10)及将float改成numeric
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/5149348.html
Copyright © 2011-2022 走看看