# AMD(Async Module Definition)

# 特点

  • 异步加载
  • 依赖前置
    define(['a','b], function() {
      a.do();
      b.do();
    });
    
  • 提前执行 define 声明依赖的模块式就加载并执行模块内的代码

# 规范化产出

RequireJS 对模块定义的规范化产出:

  • define() 定义模块
  • require() 加载模块
  • require.config() 指定资源路径

# 实现方式

异步加载

// 创建script标签节点
req.createNode = function (config, moduleName, url) {
  var node = config.xhtml
    ? document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script')
    : document.createElement('script')
  node.type = config.scriptType || 'text/javascript'
  node.charset = 'utf-8'
  node.async = true
  return node
}

在这段代码中我们可以看出, requirejs 导入模块的方式实际就是创建脚本标签,一切的模块都需要经过这个方法创建。

注:在 req.load 函数中调用 createNode,监听 script load 事件,同时考虑浏览器兼容问题,并设置 script 的 src、将 script 添加到页面, 加载成功后调用 context.onScriptLoad,onScriptLoad 再调用 completeLoad(详情请参考源码)

# 组织依赖关系

通过 defQueue、defQyeyeMap 来管理,name 对应的 deps 全部加载完成后,执行 callback。

  • context.defQueue.push([name, deps, callback]);
  • context.defQueueMap[name] = true;

# 核心思想

RequireJS 加载模块的核心思想是利用了动态加载脚本的异步性以及 onload 事件

  • 在 HTML 中引入 < script> 标签是同步加载;
  • 在脚本中动态加载是异步加载,且由于被加载的脚本在事件队列的后端,因此总是会在当前脚本之后执行;
  • 使用 onload 和 onerror 事件可以监听脚本加载完成,以异步的事件来处理异步的事件

# 机制

  • RequireJS 使用 head.appendChild()将每一个依赖加载为一个 script 标签。
  • RequireJS 等待所有的依赖加载完毕,计算出模块定义函数正确调用顺序,然后依次调用它们。

# 模拟模块化加载

const myRequire = (deps, callback) => {
  //记录模块加载数量
  let ready = 0

  //创建脚本标签
  function load(url) {
    let script = document.createElement('script')
    script.type = 'text/javascript'
    script.async = true
    script.src = url
    return script
  }

  let nodes = []
  for (let i = deps.length - 1; i >= 0; i--) {
    nodes.push(load(deps[i]))
  }
  //加载脚本
  for (let i = nodes.length - 1; i >= 0; i--) {
    nodes[i].addEventListener(
      'load',
      () => {
        ready++
        //如果所有依赖脚本加载完成,则执行回调函数;
        if (ready === nodes.length) {
          callback()
        }
      },
      false
    )
    document.head.appendChild(nodes[i])
  }
}

myRequire(['module1.js', 'module2.js', 'module3.js'], () => {
  console.log('ready!')
})

# 缓存策略

require()的缓存策略:node.js 会自动缓存经过 require 引入的文件,使得下次再引入不需要经过文件系统而是直接从缓存中读取。不过这种缓存方式是经过文件路径定位的,即使两个完全相同的文件,但是位于不同的路径下,会在缓存中维持两份。可以通过 console.log(require.cache)获取目前在缓存中的所有文件。

# exports 与 module.exports 区别

require 导出的内容是 module.exports 的指向的内存块内容,并不是 exports 的。简而言之,区分他们之间的区别就是 exports 只是 module.exports 的引用,辅助后者添加内容用的。