CommonJS

  • 最开始比拟成熟的模块化思维是来自CommonJs
  • 其次要利用于Node.js
  • 每个文件就是一个Module实例
  • 通过require引入,通过moudule裸露内容
  • 每个模块被加载一次后会被缓存,所以文件中除了裸露内容外,一些同步逻辑(如console.log)只会触发一次
  • 文件的加载是同步进行的
  • 沙箱编译,require的js代码会被革新成立即执行函数,而后将运行得出的module.exports对象缓存,供下次应用,这也是为什么js文件只会被执行一次
  • 导出的是值得拷贝,区别于ES6模块,前面会提及

AMD

  • 客户端看到服务端有这个模块化的机制,也想引进,然而因为CommonJs是同步加载的,在服务器,因为文件都在文件系统中,读取加载很快,但客户端里,资源须要通过网络申请,仍旧采纳同步的办法,会导致性能骤降
  • 出于上述思考,在客户端上提出了异步加载模块的标准,即AMD(Asynchronous Module Definition)
  • 模块的加载不会影响其后续代码的运行
  • 所有依赖该模块的代码,会被放到一个回调函数中,等加载结束后处理
  • 通过require引入模块,除了模块外,还须要传入一个回调函数,在模块加载实现时调用
  • 通过define定义模块,加载实现后会执行factory
define(id?, dependencies?, factory);

RequireJS

  • RequireJS是基于AMD标准的一种实现
  • 原理是通过动态创建script标签,插入dom中,并在onload事件中执行回调
  • 加载过的模块会对其缓存

CMD

  • 相似于AMD,阿里本人搞了个CMD的标准,即(Common Module Definition)
  • 其通过define定义模块,require引入,通过exports或return裸露变量

SeaJS

  • SeaJS是CMD标准的一种实现
  • 与RequireJS不同,seaJS采纳的是就近执行,RequireJS是前置执行
  • 模块的加载都是异步的,但执行上,RequireJS会默认先把所有的依赖都执行了,而SeaJS会依照你在代码中定义的程序来执行
  • 加载过的模块会对其缓存

ES6

  • ES6问世后,模块化的概念被写进了ES里,提出了官网的模块化标准
  • 通过import引入,export导出,这里的export和import是关键字,之前的define和require等都是第三方包定义的全局办法
  • 与CommonJS不同的是,ES6输入的是值援用,CommonJS输入的是值拷贝
// test.jsconst test = { a: 1 };export.test = test;export.add = () => test.a = test.a + 1;// index.js// in commonjsconst { test, add } = require(./test);// or in es6// import { test, add } from './test';add();console.log(test.a)
  • 上述例子,ES6输入的是2,CommonJS输入的是2,CommonJS输入是1
  • CommonJS的导出原理能够参考webpack针对CommonJS模块的打包解决,其原理是通过执行脚本,将导出的对象保留在外部的一个模块汇合中,下次再读取模块就间接从该模块中取值,这里保留导出对象就是一个间接赋值的过程,所以CommonJS的导出是一个浅拷贝的过程,另外内部模块通过require引入的变量,其实是通过解构赋值,或间接赋值的模式引入的,且这些引入的变量能够批改其值,所以如果引入的是根本类型,后续批改他不会对输入造成影响,但如果引入的是对象,批改属性,会对导出模块中对应的属性造成影响
  • ES6中,JS引擎在动态分析阶段遇到import命令时,会生成一个只读援用,后续逻辑都是基于这个援用做的解决,当真正执行脚本时,才依据这个援用取对应模块里取值,所以他也不会缓存export,整个模块在运行环境里能够认为是一个对象,导出的变量都是绑定在这个模块上的,并且通过import引入的变量是不能批改其值的,要批改只能应用模块里自身的办法,这就保障了引入的变量永远是追随模块中的变量发生变化的
  • 同理,基于ES6的运行原理,它属于编译时加载(动态加载),在编译阶段就能够晓得引入的是哪个变量,对应输入他的援用,但CommonJS须要执行了整个模块,对应生成一个输入的对象,依据这个对象能力决定引入的值,这种模式称为运行时加载
  • 运行时加载因为须要运行整个代码块,所以无奈做动态优化,编译时加载因为在编译阶段就晓得须要的变量,所以能够在编译阶段动静删除无用的代码,缩小打包的体积
  • 留神几个例子
// test.jslet count = 1;export default {    count,    add() { count++; },    get() { return count };}// index.jsimport test from './test.js'console.log(test.count); // 1test.add();console.log(test.count); // 1console.log(test.get()); // 2
  • 这里test.add并没有扭转test.count的值,是因为count是根本类型,导出到export的count中属于值拷贝,后续操作不会扭转他的值,想要扭转count的值能同步更新,应该把他输入到导出中,即
// test.jsexport let count = 1;export function add() { count++; }export function get() { return count }// index.jsimport {count, add, get} from './test.js'console.log(count); // 1add();console.log(count); // 2console.log(get()); // 2
  • count当初作为输入,即便他是一个根本类型,内部模块扭转了它,输入也会同步更新,是因为内部引入的count曾经是一个援用,指向的是模块中的数值