zp1112 / blog

地址
http://issue.suzper.com/
36 stars 3 forks source link

快速理解commonjs和AMD和CMD模块加载区别 #16

Open zp1112 opened 6 years ago

zp1112 commented 6 years ago

今天来梳理一下commonjs和AMD和CMD三种模块加载规范。

commonjs服务器端模块加载

commonjs是nodejs在服务器端的模块规范,很多的后端语言比如python和java都有模块的概念,而nodejs作为js语法,也需要实现一套模块加载器,才能更好地作为服务端语言。

使用方式

// a.js
console.log(444);
const privateVar = 'haha';
module.exports = {
  name: 'candy',
  sex: 1
}
// main.js
const a = require('./a.js'); // 444
console.log(a.name); // candy

以上代码可以看到,一个单独的文件就是一个模块,模块的内部变量无法被外部知道,除非定义为global变量,或者通过接口方式暴露出来。使用module.exports输出,使用require加载,es6中可以使用export输出,使用import加载,require和import的区别在于require是运行时加载执行模块,而import是编译时加载并执行。 可以看到,以上require是同步的代码,所以需要先加载好了模块,才能进行模块接口的调用,而这在浏览器端是无法适用的,因为浏览器端的js是从服务器端获取,他的加载速度取决于网络环境等多种因素,而服务器端的js直接从硬盘加载。因此AMD和CMD浏览器端模块规范应运而生。

AMD/RequireJS

AMD(Asynchronous Module Definition)异步模块定义,它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这种加载方式解决了浏览器端的两个问题

  1. 多个js文件加载的时候依赖的模块需要早于当前模块加载
  2. js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长

使用方式

// a.js
define(['./b.js'], function(b) {
  return {
    name: 'candy',
    sex: 1
  }
})
// index.html
<script>
require(['./a.js'], function(a) {
  console.log(a.name);
}); // 444 candy
</script>

define的工厂方法factory,是模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次,它的参数是前面依赖数组加载的顺序得到的模块,如果数组为空,参数默认为require,exports,module。如果是对象,此对象应该为模块的输出值。这里的a.name的使用和a模块的加载是异步的,因此不会阻塞浏览器。 requirejs使用模块的方式也是require,但是这个require和commonjs的不一样的,它是异步的。

CMD/seajs

CMD(Common Module Definition)通用模块定义,它和requirejs都是为了解决浏览器端模块加载,但是区别在于模块的加载机制和加载时机上的不同,AMD是依赖关系前置,在定义模块的时候就要声明其依赖的模块,并且执行模块;CMD是按需加载依赖就近,只有在用到某个模块的时候才会去执行

// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  var b = require('./b') // 依赖可以就近书写,cmd在静态解析的时候将工厂函数toString,正则匹配到require,然后将依赖加载,在使用的时候执行
  b.doSomething()
})
// AMD
define(['./a', './b'], function(a, b) { // 依赖前置,在静态解析的时候就已经执行。
  a.doSomething()
  b.doSomething()
})

// b.js
define(function(require, exports, module) {
  console.log(888)
  exports.bdata = 2;
});

// a.js
define(function(require, exports, module) {
  const b = require('./b'); // 此处不会打印888因为没有使用b
  b.bdata; // 此处会打印888,因为使用了b的接口
  exports.adata = 1;
});

// index.html
seajs.use(['a.js'], function(my){
  var star= my.adata;
  console.log(star);  //1
});

CMD中的工厂函数,它只是define函数的一个参数,并不会被直接执行,而是会在需要的时候由专门的函数来调用生成接口。所以, 一个模块文件被浏览器下载下来后,并不会直接运行我们的模块定义代码,而是会首先执行一个define函数,这个函数会取得模块定义的源代码(通过函数的toString()函数来取得源代码),然后利用正则匹配找到依赖的模块(匹配require("dep.js")这样的字符串),然后加载依赖的模块,最后发射一个自定义事件complete,通知当前模块, 模块已经加载完成,此时,当前模块的就会调用与complete事件绑定的回调函数,完成与这个模块相关的任务,比如resolve与这个模块加载绑定的Promise。 作者:知乎用户 链接:https://www.zhihu.com/question/21308647/answer/118271737 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

boom!!!