requireJS为何不会多次加载同一个文件?怎么理解内部机制?

文件目录如下:

root-|index.html
     |require.js
     |main.js
     |jquery.min.js
     |math.js

index.html:

<head>
  <script type="text/javascript" src="require.js" data-main="main" defer async="true"></script>
</head>

main.js:

require.config({ 
  paths: { 
    "jquery": "jquery.min", 
    "math": "math"
  } 
}); 
require(['jquery','math'], function ($,math){ 
    console.log('main'); 
}); 

math.js:

define(['jquery'],function($){
    console.log('math');
    var add = function(a,b){
        return a+b;
    };
    return{
        add: add
    };
});

在浏览器的Network可以看到加载js的情况:
图片描述

疑问:
main.js模块依赖了jquery模块和math模块,由于异步我视为开了两个线程;第二个math线程又依赖了jquery模块,为何没有再一次请求该资源?requireJS内部机制是怎样的?

阅读 9.2k
2 个回答

如果你有时间去看看 sea 或者 requirejs 的源码就能明白,其实原理并不复杂。

如果没有显式的指定名称,你的源文件路径(含目录和文件名)就是模块的名称。当然它内部会进行一些处理,使名称能唯一对应一个模块的定义。

那么模块的定义是一个 function,这个 function 实际是一个 factory(工厂模式,不明白的话就去搜下),这个 factory 在需要使用的时候(require("xxxx") 的时候)才有可能会被调用。为什么是有可能?因为如果检查到已经调用过,已经生成了模块实例,就直接返回模块实例,而不再次调用工厂方法了。

这个过程大概是这样的(非常简化的,只是个示意,加载和依赖都忽略了,想了解详情自己去看源码)

class Module {
    constructor(name, factory) {
        this.name = name;
        this.factory = factory;
        this.instance = null;
    }
}

// 非常简化的 define 方法(不要问我依赖的问题)
function define(factory) {
    const name = "";    // 根据源码路径之类的来计算
    // allModules 是框架维护的一个模块字典
    allModules[name] = new Module(name, factory);
}

// 非常简化的 require 方法
function require(name) {
    const module = allModules[name];
    if (!module.instance) {
        const moduleInst = { exports: {} };
        // 如果 factory 有返回值就用返回值赋给 module.instance
        // 否则就用 moduleInst 赋给它
        module.instance = module.factory(moduleInst.exports, moduleInst) || moduleInst;
    }

    return module.instance;
}

对于加载的外部模块也说,也是一样的。尤其是像 jQuery 这些,在写源码的时候已经考虑了兼容 AMD 的问题,所以可以很好的用于 requirejs 依赖加载。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏