Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zp1112 opened this issue May 20, 2018 · 0 comments
Open

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

zp1112 opened this issue May 20, 2018 · 0 comments

Comments

@zp1112
Copy link
Owner

zp1112 commented May 20, 2018

今天来梳理一下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!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant