zoukankan      html  css  js  c++  java
  • JavaScript AMD 模块加载器原理与实现

    关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下。

    1. 模块加载器

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

    2. AMD规范与接口定义

    在实现之前,我们需要拟定实现的API,然后才能进行下一步的编码。出于学习的目的,并没有完全实现 AMD规范 中定义的内容,简单实现的API如下:

     1 // 定义模块
     2 define(id?, dependencies?, factory);
     3 
     4 // 调用模块
     5 require(dependencies?, factory);
     6 
     7 // 模块加载器配置
     8 require.config({
     9     paths: {},
    10     shim: {
    11         'xx': {
    12             deps: [],
    13             exports: ''
    14         }
    15     }
    16     
    17 });
    18 
    19 // 模块加载器标识
    20 define.amd = {};

    假如我们有以下的开发目录:

    1     scripts
    2         |-- a.js
    3         |-- b.js
    4         |-- c.js
    5         |-- d.js
    6         |-- main.js
    7     define.js
    8     index.html

    除了 define.js 为需要实现的内容,各个文件的大概内容为:

     1 // a.js
     2 define(['b'], function(b) {
     3     
     4     return {
     5         say: function() {
     6             return 'a call: ' + b;
     7         }
     8     };
     9         
    10 });
    11 
    12 
    13 // b.js
    14 define(function() {
    15     return 'this is b';
    16 });    
    17 
    18 
    19 // c.js
    20 (function(global) {
    21     global.NotAmd = function() {
    22         return 'c, not amd module';
    23     }
    24 })(window);
    25 
    26 
    27 // d.js
    28 define(['b'], function(b) {
    29     
    30     return {
    31         say: function() {
    32             return 'd call: ' + b;
    33         }
    34     };
    35         
    36 });
    37 
    38 
    39 // main.js
    40 require.config({
    41     paths: {
    42         'notAmd': './c'
    43     },
    44     shim: {
    45         'notAmd': {
    46             exports: 'NotAmd'
    47         }
    48     }
    49 });
    50     
    51 require(['a', 'notAmd', 'd'], function(a, notAmd, d) {
    52     console.log(a.say());           // should be: a call: this is b
    53     console.log(notAmd());       // should be: c, not amd module
    54     console.log(d.say());           // should be: d call: this is b
    55 });
    56 
    57 
    58 // index.html
    59 <script src="vendors/define.js" data-main="scripts/main"></script>

    上面的代码完全兼容于 Requirejs,将 define.js 换成 Requirejs,上面的代码就能成功跑起来。这里我们需要实现 define.js 来达到同样的效果。

    3. 实现

    一个文件对于一个模块。先看一下模块加载器的主要执行流程:

     整个流程其实就是加载主模块(data-main指定的模块,里面有require调用),然后加载require的依赖模块,当所有的模块及其依赖模块都已加载完毕,执行require调用中的factory方法。

    在实现过程中需要考虑到的点有:

    1. 构造一个对象,用以保存模块的标识、依赖、工厂方法等信息。

    2. 非AMD模块的支持。非AMD模块不会调用define方法来定义自己,如果不支持非AMD模块,那么该模块在加载完毕之后流程会中断,其exports的结果也不对。

    3. 采用url来作为模块标识,由于url的唯一性,不同目录同id的模块就不会相互覆盖。

    4. 循环依赖。可分为两种依赖方式:

     1 // 弱依赖:不在factory中直接执行依赖模块的方法
     2 // a.js
     3 define(['b'], function(b) {
     4     return {
     5         say: function() {
     6             b.say();
     7         }
     8     }
     9 });
    10 
    11 // b.js
    12 define(['a'], function(a) {
    13     return {
    14         say: function(a) {
    15             a.say();
    16         }
    17     }
    18 });
    19 
    20 // 强依赖:直接在factory中执行依赖模块的方法
    21 // a.js
    22 define(['b'], function(b) {
    23     b.say();
    24              
    25     return {
    26          say: function() {
    27              return 'this is a';
    28          }
    29      }
    30 });
    31 
    32 // b.js
    33 define(['a'], function(a) {
    34     a.say();
    35             
    36     return {
    37         say: function() {
    38             return 'this is b';
    39         }
    40     }
    41 });

    对于弱依赖,程序的解决方式是首先传递undefined作为其中一个依赖模块的exports结果,当该依赖模块的factory成功执行后,其就能返回正确的exports值。对于强依赖,程序会异常。但是如果确实在应用中发生了强依赖,我们可以用另外一种方式去解决,那就是模块加载器会传递该模块的exports参数给factory,factory直接将方法挂载在exports上。其实这也相当于将其转换为了弱依赖。不过大部分情况下,程序里面发生了循环依赖,往往是我们的设计出现了问题。

     

    好了,下面是 define.js 实现的代码:

      1 /*jslint regexp: true, nomen: true, sloppy: true */
      2 /*global window, navigator, document, setTimeout, opera */
      3 (function(global, undefined) {
      4     var document = global.document,
      5         head = document.head || document.getElementsByTagName('head')[0] || document.documentElement,
      6         baseElement = document.getElementsByTagName('base')[0],
      7         noop = function(){},
      8         currentlyAddingScript, interactiveScript, anonymousMeta,
      9         dirnameReg = /[^?#]*//,
     10         dotReg = //.//g,
     11         doubleDotReg = //[^/]+/..//,
     12         multiSlashReg = /([^:/])/+//g,
     13         ignorePartReg = /[?#].*$/,
     14         suffixReg = /.js$/,
     15 
     16         seed = {
     17             // 缓存模块
     18             modules: {},
     19             config: {
     20                 baseUrl: '',
     21                 charset: '',
     22                 paths: {},
     23                 shim: {},
     24                 urlArgs: ''
     25             }
     26         };
     27 
     28     /* utils */
     29     function isType(type) {
     30         return function(obj) {
     31             return {}.toString.call(obj) === '[object ' + type + ']';
     32         }
     33     }
     34 
     35     var isFunction = isType('Function');
     36     var isString = isType('String');
     37     var isArray = isType('Array');
     38 
     39 
     40     function hasProp(obj, prop) {
     41         return Object.prototype.hasOwnProperty.call(obj, prop);
     42     }
     43 
     44     /**
     45      * 遍历数组,回调返回 true 时终止遍历
     46      */
     47     function each(arr, callback) {
     48         var i, len;
     49 
     50         if (isArray(arr)) {
     51             for (i = 0, len = arr.length; i < len; i++) {
     52                 if (callback(arr[i], i, arr)) {
     53                     break;
     54                 }
     55             }
     56         }
     57     }
     58 
     59     /**
     60      * 反向遍历数组,回调返回 true 时终止遍历
     61      */
     62     function eachReverse(arr, callback) {
     63         var i;
     64 
     65         if (isArray(arr)) {
     66             for (i = arr.length - 1; i >= 0; i--) {
     67                 if (callback(arr[i], i, arr)) {
     68                     break;
     69                 }
     70             }
     71         }
     72     }
     73 
     74     /**
     75      * 遍历对象,回调返回 true 时终止遍历
     76      */
     77     function eachProp(obj, callback) {
     78         var prop;
     79         for (prop in obj) {
     80             if (hasProp(obj, prop)) {
     81                 if (callback(obj[prop], prop)) {
     82                     break;
     83                 }
     84             }
     85         }
     86     }
     87 
     88     /**
     89      * 判断是否为一个空白对象
     90      */
     91     function isPlainObject(obj) {
     92         var isPlain = true;
     93 
     94         eachProp(obj, function() {
     95             isPlain = false;
     96             return true;
     97         });
     98 
     99         return isPlain;
    100     }
    101 
    102     /**
    103      * 复制源对象的属性到目标对象中
    104      */
    105     function mixin(target, source) {
    106         if (source) {
    107             eachProp(source, function(value, prop) {
    108                 target[prop] = value;
    109             });
    110         }
    111         return target;
    112     }
    113 
    114     function makeError(name, msg) {
    115         throw new Error(name + ":" + msg);
    116     }
    117 
    118     /**
    119      * 获取全局变量值。允许格式:a.b.c
    120      */
    121     function getGlobal(value) {
    122         if (!value) {
    123             return value;
    124         }
    125         var g = global;
    126         each(value.split('.'), function(part) {
    127             g = g[part];
    128         });
    129         return g;
    130     }
    131 
    132 
    133     /* path */
    134     /**
    135      * 获取path对应的目录部分
    136      *
    137      * a/b/c.js?foo=1#d/e  --> a/b/
    138      */
    139     function dirname(path) {
    140         var m = path.match(dirnameReg);
    141 
    142         return m ? m[0] : "./";
    143     }
    144 
    145     /**
    146      * 规范化path
    147      *
    148      * http://test.com/a//./b/../c  -->  "http://test.com/a/c"
    149      */
    150     function realpath(path) {
    151         // /a/b/./c/./d --> /a/b/c/d
    152         path = path.replace(dotReg, "/");
    153 
    154         // a//b/c --> a/b/c
    155         // a///b////c --> a/b/c
    156         path = path.replace(multiSlashReg, "$1/");
    157 
    158         // a/b/c/../../d --> a/b/../d --> a/d
    159         while (path.match(doubleDotReg)) {
    160             path = path.replace(doubleDotReg, "/");
    161         }
    162 
    163         return path;
    164     }
    165 
    166     /**
    167      * 将模块id解析为对应的url
    168      *
    169      * rules:
    170      * baseUrl: http://gcfeng.github.io/blog/js
    171      * host: http://gcfeng.github.io/blog
    172      *
    173      * http://gcfeng.github.io/blog/js/test.js  -->  http://gcfeng.github.io/blog/js/test.js
    174      *                                    test  -->  http://gcfeng.github.io/blog/js/test.js
    175      *                              ../test.js  -->  http://gcfeng.github.io/blog/test.js
    176      *                                /test.js  -->  http://gcfeng.github.io/blog/test.js
    177      *                            test?foo#bar  -->  http://gcfeng.github.io/blog/test.js
    178      *
    179      * @param {String} id 模块id
    180      * @param {String} baseUrl 模块url对应的基地址
    181      */
    182     function id2Url(id, baseUrl) {
    183         var config = seed.config;
    184 
    185         id = config.paths[id] || id;
    186 
    187         // main///test?foo#bar  -->  main/test?foo#bar
    188         id = realpath(id);
    189 
    190         // main/test?foo#bar  -->  main/test
    191         id = id.replace(ignorePartReg, "");
    192 
    193         id = suffixReg.test(id) ? id : (id + '.js');
    194 
    195         id = realpath(dirname(baseUrl) + id);
    196 
    197         id = id + (config.urlArgs || "");
    198 
    199         return id;
    200     }
    201 
    202 
    203     function getScripts() {
    204         return document.getElementsByTagName('script');
    205     }
    206 
    207     /**
    208      * 获取当前正在运行的脚本
    209      */
    210     function getCurrentScript() {
    211         if (currentlyAddingScript) {
    212             return currentlyAddingScript;
    213         }
    214 
    215         if (interactiveScript && interactiveScript.readyState === 'interactive') {
    216             return interactiveScript;
    217         }
    218 
    219         if (document.currentScript) {
    220             return interactiveScript = document.currentScript;
    221         }
    222 
    223         eachReverse(getScripts(), function (script) {
    224             if (script.readyState === 'interactive') {
    225                 return (interactiveScript = script);
    226             }
    227         });
    228         return interactiveScript;
    229     }
    230 
    231     /**
    232      * 请求JavaScript文件
    233      */
    234     function loadScript(url, callback) {
    235         var config = seed.config,
    236             node = document.createElement('script'),
    237             supportOnload = 'onload' in node;
    238 
    239         node.charset = config.charset || 'utf-8';
    240         node.setAttribute('data-module', url);
    241 
    242         // 绑定事件
    243         if (supportOnload) {
    244             node.onload = function() {
    245                 onload();
    246             };
    247             node.onerror = function() {
    248                 onload(true);
    249             }
    250         } else {
    251             node.onreadystatechange = function() {
    252                 if (/loaded|complete/.test(node.readyState)) {
    253                     onload();
    254                 }
    255             }
    256         }
    257 
    258         node.async = true;
    259         node.src = url;
    260 
    261         // 在IE6-8浏览器中,某些缓存会导致结点一旦插入就立即执行脚本
    262         currentlyAddingScript = node;
    263 
    264         // ref: #185 & http://dev.jquery.com/ticket/2709
    265         baseElement ? head.insertBefore(node, baseElement) : head.appendChild(node);
    266 
    267         currentlyAddingScript = null;
    268 
    269 
    270         function onload(error) {
    271             // 保证执行一次
    272             node.onload = node.onerror = node.onreadystatechange = null;
    273             // 删除脚本节点
    274             head.removeChild(node);
    275             node = null;
    276             callback(error);
    277         }
    278     }
    279 
    280 
    281 
    282     // 记录模块的状态信息
    283     Module.STATUS = {
    284         // 初始状态,此时模块刚刚新建
    285         INITIAL: 0,
    286         // 加载module.url指定资源
    287         FETCH: 1,
    288         // 保存module的依赖信息
    289         SAVE: 2,
    290         // 解析module的依赖内容
    291         LOAD: 3,
    292         // 执行模块,exports还不可用
    293         EXECUTING: 4,
    294         // 模块执行完毕,exports可用
    295         EXECUTED: 5,
    296         // 出错:请求或者执行出错
    297         ERROR: 6
    298     };
    299 
    300     function Module(url, deps) {
    301         this.url = url;
    302         this.deps = deps || [];                 // 依赖模块列表
    303         this.dependencies = [];                 // 依赖模块实例列表
    304         this.refs = [];                         // 引用模块列表,用于模块加载完成之后通知其引用模块
    305         this.exports = {};
    306         this.status = Module.STATUS.INITIAL;
    307 
    308         /*
    309          this.id
    310          this.factory
    311          */
    312     }
    313 
    314     Module.prototype = {
    315         constructor: Module,
    316 
    317         load: function() {
    318             var mod = this,
    319                 STATUS = Module.STATUS,
    320                 args = [];
    321 
    322             if (mod.status >= STATUS.LOAD) {
    323                 return mod;
    324             }
    325             mod.status = STATUS.LOAD;
    326 
    327             mod.resolve();
    328             mod.pass();
    329             mod.checkCircular();
    330 
    331             each(mod.dependencies, function(dep) {
    332                 if (dep.status < STATUS.FETCH) {
    333                     dep.fetch();
    334                 } else if (dep.status === STATUS.SAVE) {
    335                     dep.load();
    336                 } else if (dep.status >= STATUS.EXECUTED) {
    337                     args.push(dep.exports);
    338                 }
    339             });
    340 
    341             mod.status = STATUS.EXECUTING;
    342 
    343             // 依赖模块加载完成
    344             if (args.length === mod.dependencies.length) {
    345                 args.push(mod.exports);
    346                 mod.makeExports(args);
    347                 mod.status = STATUS.EXECUTED;
    348                 mod.fireFactory();
    349             }
    350         },
    351 
    352         /**
    353          * 初始化依赖模块
    354          */
    355         resolve: function() {
    356             var mod = this;
    357 
    358             each(mod.deps, function(id) {
    359                 var m, url;
    360 
    361                 url = id2Url(id, seed.config.baseUrl);
    362                 m = Module.get(url);
    363                 m.id = id;
    364                 mod.dependencies.push(m);
    365             });
    366         },
    367 
    368         /**
    369          * 传递模块给依赖模块,用于依赖模块加载完成之后通知引用模块
    370          */
    371         pass: function() {
    372             var mod = this;
    373 
    374             each(mod.dependencies, function(dep) {
    375                 var repeat = false;
    376 
    377                 each(dep.refs, function(ref) {
    378                     if (ref === mod.url) {
    379                         repeat = true;
    380                         return true;
    381                     }
    382                 });
    383 
    384                 if (!repeat) {
    385                     dep.refs.push(mod.url);
    386                 }
    387             });
    388         },
    389 
    390         /**
    391          * 解析循环依赖
    392          */
    393         checkCircular: function() {
    394             var mod = this,
    395                 STATUS = Module.STATUS,
    396                 isCircular = false,
    397                 args = [];
    398 
    399             each(mod.dependencies, function(dep) {
    400                 isCircular = false;
    401                 // 检测是否存在循环依赖
    402                 if (dep.status === STATUS.EXECUTING) {
    403                     each(dep.dependencies, function(m) {
    404                         if (m.url === mod.url) {
    405                             // 存在循环依赖
    406                             return isCircular = true;
    407                         }
    408                     });
    409 
    410                     // 尝试解决循环依赖
    411                     if (isCircular) {
    412                         each(dep.dependencies, function(m) {
    413                             if (m.url !== mod.url && m.status >= STATUS.EXECUTED) {
    414                                 args.push(m.exports);
    415                             } else if (m.url === mod.url) {
    416                                 args.push(undefined);
    417                             }
    418                         });
    419 
    420                         if (args.length === dep.dependencies.length) {
    421                             // 将exports作为最后一个参数传递
    422                             args.push(dep.exports);
    423                             try {
    424                                 dep.exports = isFunction(dep.factory) ? dep.factory.apply(global, args) : dep.factory;
    425                                 dep.status = STATUS.EXECUTED;
    426                             } catch (e) {
    427                                 dep.exports = undefined;
    428                                 dep.status = STATUS.ERROR;
    429                                 makeError("Can't fix circular dependency", mod.url + " --> " + dep.url);
    430                             }
    431                         }
    432                     }
    433                 }
    434             });
    435         },
    436 
    437         makeExports: function(args) {
    438             var mod = this,
    439                 result;
    440 
    441             result = isFunction(mod.factory) ? mod.factory.apply(global, args) : mod.factory;
    442             mod.exports = isPlainObject(mod.exports) ? result : mod.exports;
    443         },
    444 
    445         /**
    446          * 模块执行完毕,触发引用模块回调
    447          */
    448         fireFactory: function() {
    449             var mod = this,
    450                 STATUS = Module.STATUS;
    451 
    452             each(mod.refs, function(ref) {
    453                 var args = [];
    454                 ref = Module.get(ref);
    455 
    456                 each(ref.dependencies, function(m) {
    457                     if (m.status >= STATUS.EXECUTED) {
    458                         args.push(m.exports);
    459                     }
    460                 });
    461 
    462                 if (args.length === ref.dependencies.length) {
    463                     args.push(ref.exports);
    464                     ref.makeExports(args);
    465                     ref.status = STATUS.EXECUTED;
    466                     ref.fireFactory();
    467                 } else {
    468                     ref.load();
    469                 }
    470             });
    471         },
    472 
    473         /**
    474          * 发送请求加载资源
    475          */
    476         fetch: function() {
    477             var mod = this,
    478                 STATUS = Module.STATUS;
    479 
    480             if (mod.status >= STATUS.FETCH) {
    481                 return mod;
    482             }
    483             mod.status = STATUS.FETCH;
    484 
    485             loadScript(mod.url, function(error) {
    486                 mod.onload(error);
    487             });
    488         },
    489 
    490         onload: function(error) {
    491             var mod = this,
    492                 config = seed.config,
    493                 STATUS = Module.STATUS,
    494                 shim, shimDeps;
    495 
    496             if (error) {
    497                 mod.exports = undefined;
    498                 mod.status = STATUS.ERROR;
    499                 mod.fireFactory();
    500                 return mod;
    501             }
    502 
    503             // 非AMD模块
    504             shim = config.shim[mod.id];
    505             if (shim) {
    506                 shimDeps = shim.deps || [];
    507                 mod.save(shimDeps);
    508                 mod.factory = function() {
    509                     return getGlobal(shim.exports);
    510                 };
    511                 mod.load();
    512             }
    513 
    514             // 匿名模块
    515             if (anonymousMeta) {
    516                 mod.factory = anonymousMeta.factory;
    517                 mod.save(anonymousMeta.deps);
    518                 mod.load();
    519                 anonymousMeta = null;
    520             }
    521         },
    522 
    523         save: function(deps) {
    524             var mod = this,
    525                 STATUS = Module.STATUS;
    526 
    527             if (mod.status >= STATUS.SAVE) {
    528                 return mod;
    529             }
    530             mod.status = STATUS.SAVE;
    531 
    532             each(deps, function(d) {
    533                 var repeat = false;
    534                 each(mod.dependencies, function(d2) {
    535                     if (d === d2.id) {
    536                         return repeat = true;
    537                     }
    538                 });
    539 
    540                 if (!repeat) {
    541                     mod.deps.push(d);
    542                 }
    543             });
    544         }
    545     };
    546 
    547 
    548     /**
    549      * 初始化模块加载
    550      */
    551     Module.init = function() {
    552         var script, scripts, initMod, url;
    553 
    554         if (document.currentScript) {
    555             script = document.currentScript;
    556         } else {
    557             // 正常情况下,在页面加载时,当前js文件的script标签始终是最后一个
    558             scripts = getScripts();
    559             script = scripts[scripts.length - 1];
    560         }
    561         initMod = script.getAttribute("data-main");
    562         // see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
    563         url = script.hasAttribute ? script.src : script.getAttribute("src", 4);
    564 
    565         // 如果seed是通过script标签内嵌到页面,baseUrl为当前页面的路径
    566         seed.config.baseUrl = dirname(initMod || url);
    567 
    568         // 加载主模块
    569         if (initMod) {
    570             Module.use(initMod.split(","), noop, Module.guid());
    571         }
    572 
    573         scripts = script = null;
    574     };
    575 
    576     /**
    577      * 生成一个唯一id
    578      */
    579     Module.guid = function() {
    580         return "seed_" + (+new Date()) + (Math.random() + '').slice( -8 );
    581     };
    582 
    583     /**
    584      * 获取一个模块,如果不存在则新建
    585      *
    586      * @param url
    587      * @param deps
    588      */
    589     Module.get = function(url, deps) {
    590         return seed.modules[url] || (seed.modules[url] = new Module(url, deps));
    591     };
    592 
    593     /**
    594      * 加载模块
    595      *
    596      * @param {Array} ids 依赖模块的id列表
    597      * @param {Function} callback 模块加载完成之后的回调函数
    598      * @param {String} id 模块id
    599      */
    600     Module.use = function(ids, callback, id) {
    601         var config = seed.config,
    602             mod, url;
    603 
    604         ids = isString(ids) ? [ids] : ids;
    605         url = id2Url(id, config.baseUrl);
    606         mod = Module.get(url, ids);
    607         mod.id = id;
    608         mod.factory = callback;
    609 
    610         mod.load();
    611     };
    612 
    613     // 页面已经存在AMD加载器或者seed已经加载
    614     if (global.define) {
    615         return;
    616     }
    617 
    618     define = function(id, deps, factory) {
    619         var currentScript, mod;
    620 
    621         // define(factory)
    622         if (isFunction(id)) {
    623             factory = id;
    624             deps = [];
    625             id = undefined;
    626 
    627         }
    628 
    629         // define(deps, factory)
    630         else if (isArray(id)) {
    631             factory = deps;
    632             deps = id;
    633             id = undefined;
    634         }
    635 
    636         if (!id && (currentScript = getCurrentScript())) {
    637             id = currentScript.getAttribute("data-module");
    638         }
    639 
    640         if (id) {
    641             mod = Module.get(id);
    642             mod.factory = factory;
    643             mod.save(deps);
    644             mod.load();
    645         } else {
    646             anonymousMeta = {
    647                 deps: deps,
    648                 factory: factory
    649             };
    650         }
    651     };
    652 
    653     define.amd = {};
    654 
    655     require = function(ids, callback) {
    656         // require("test", callback)
    657         if (isString(ids)) {
    658             makeError("Invalid", "ids can't be string");
    659         }
    660 
    661         // require(callback)
    662         if (isFunction(ids)) {
    663             callback = ids;
    664             ids = [];
    665         }
    666 
    667         Module.use(ids, callback, Module.guid());
    668     };
    669 
    670     require.config = function(config) {
    671         mixin(seed.config, config);
    672     };
    673 
    674 
    675     // 初始化
    676     Module.init();
    677 })(window);
    View Code

    变量 seed 保存加载过的模块和一些配置信息。对象 Module 用来描述一个模块,Module.STATUS 描述一个模块的状态信息,define.js 加载完毕之后调用 Module.init 来初始化baseUrl 和主模块。当主模块调用require方法后,程序就会去加载相关的依赖模块。

    有一个需要注意的地方是 动态创建的script,在脚本加载完毕之后,会立即执行返回的代码。对于AMD模块,其加载完毕之后会执行define方法,如果该模块为匿名模块(没有指定id),我们需要在onload回调中来处理该模块。在开始加载模块的时候,我们不会知道其依赖和工厂方法等信息,需要在这个模块加载完毕执行define方法才能获得。

    4. 参考

    Requirejs

    Seajs

  • 相关阅读:
    Concurrent
    Java多线程状态切换
    Java中volatile如何保证long和double的原子性操作
    协程与线程
    线程饥饿
    线程活锁
    线程死锁
    Java Thread之start和run方法的区别
    ThreadLocal内存泄漏
    interrupt和interrupted和isInterrupted的区别
  • 原文地址:https://www.cnblogs.com/yjfengwen/p/4198245.html
Copyright © 2011-2022 走看看