commonJS
1. 基本类型:值复制,不共享
2. 引用类型:浅拷贝,共享
3. 工作空间可以修改引入的值
ES Module
1. 只读导入,动态读取
2. 不可在外部修改引用,但可以调用引用中包含的的方法

执行顺序
commonJS
1. 检查是否有该模块的缓存
2. 如果有,则使用缓存
3. 如果没有,则执行该模块代码,并缓存
ES Module
1. 检查该模块是否引入过?
2. 是,暂时认该模块为 {}
3. 否,进入该模块并执行其代码(不做缓存)
⚠️import 会被提升到最先执行

1. 对导入值的验证

commonJS

  1. // mod.js
  2. let count = 1;
  3. let friends = ['夏洛'];
  4. function plusCount() {
  5. count++
  6. };
  7. function plusYuanhua() {
  8. friends.push('袁华');
  9. }
  10. setInterval(() => {
  11. console.log('mod.js 每秒打印 - count', count);
  12. console.log('mod.js 每秒打印 - friends', friends);
  13. }, 1000);
  14. module.exports = {
  15. count,
  16. friends,
  17. plusCount,
  18. plusYuanhua,
  19. }
  20. // index.js
  21. const mod = require('./mod.js');
  22. console.log('index.js 初次导入 - mod.count', mod.count);
  23. console.log('index.js 初次导入 - mod.friends', mod.friends);
  24. mod.plusCount();
  25. mod.plusYuanhua();
  26. console.log('index.js 执行 mod.plusCount/plusYuanhua 后 - mod.count', mod.count);
  27. console.log('index.js 执行 mod.plusCount/plusYuanhua 后 - mod.friends', mod.friends);
  28. setTimeout(() => {
  29. mod.count = 3;
  30. console.log('index.js 延时2s - mod.count', mod.count);
  31. console.log('index.js 延时2s - mod.friends', mod.friends);
  32. }, 2000)
  33. /*
  34. index.js 初次导入 - mod.count 1
  35. index.js 初次导入 - mod.friends [ '夏洛' ]
  36. index.js 执行 mod.plusCount/plusYuanhua 后 - mod.count 1
  37. index.js 执行 mod.plusCount/plusYuanhua 后 - mod.friends [ '夏洛', '袁华' ]
  38. mod.js 每秒打印 - count 2
  39. mod.js 每秒打印 - friends [ '夏洛', '袁华' ]
  40. mod.js 每秒打印 - count 2
  41. mod.js 每秒打印 - friends [ '夏洛', '袁华' ]
  42. index.js 延时2s - mod.count 3
  43. index.js 延时2s - mod.friends [ '夏洛', '袁华' ]
  44. mod.js 每秒打印 - count 2
  45. mod.js 每秒打印 - friends [ '夏洛', '袁华' ]
  46. */

ES Module

Node 13.2 增加了对 ES Module 的支持,为使 node 识别 ES Module,你需要做如下之一

  1. 更改 .js.mjs,并使用 node index.mjs
  2. package.json添加字段"type": "module",如此所有 .js 均被识别为 ES Module
  1. // index.mjs
  2. import { counter } from './mod.mjs'
  3. counter = {}; // TypeError: Assignment to constant variable.
  4. console.log('a.js-1', counter)
  5. // mod.mjs
  6. export let counter = {
  7. count: 1
  8. }
  9. setInterval(() => {
  10. console.log('modB.js-1', counter.count)
  11. }, 1000)

可见 import 为只读引入,如此,mod.mjs 中 count 变化会及时的反应在 index.mjs 中

2. 模块导入,它们做了什么?

commonJS

  1. // index.js
  2. let a = require('./modA.js')
  3. let b = require('./modB.js')
  4. console.log('index.js-1', '执行完毕', a.done, b.done)
  5. // modA.js
  6. exports.done = false
  7. let b = require('./modB.js')
  8. console.log('modA.js-1', b.done)
  9. exports.done = true
  10. console.log('modB.js-2', '执行完毕')
  11. // modB.js
  12. exports.done = false
  13. let a = require('./modA.js')
  14. console.log('modB.js-1', a.done)
  15. exports.done = true
  16. console.log('modB.js-2', '执行完毕')
  17. /*
  18. modB.js-1 false
  19. modB.js-2 执行完毕
  20. modA.js-1 true
  21. modB.js-2 执行完毕
  22. index.js-1 执行完毕 true true
  23. */

执行顺序是这样的:

  1. node 执行 index.js 文件,发现 require('./modA.js'),暂停 index.js 代码执行,进入 modA 模块
  2. 在 modA 中发现 require('./modB.js'),暂停 modA 代码执行,将已执行的部分 modA 代码缓存,随后进入 modB 模块
  3. 在 modB 中发现require('./modA.js')提取已缓存的部分 modA (因为 modA 代码全部执行完),执行所有的 modB 代码,完毕后,缓存 modB 执行结果,执行栈返回至 modA
  4. 执行 modA 剩余代码,完毕后,缓存 modA,执行栈返回 index.js
  5. 在 index.js 发现 require('./modBjs')提取已有的 modB 缓存,执行剩余代码

ES Module

  1. // index.mjs
  2. console.log("before import mod")
  3. import { b } from "./mod.mjs"
  4. console.log("b is " + b)
  5. export let a = b + 1;
  6. // mod.mjs
  7. console.log("before import index")
  8. import { b } from "./index.mjs"
  9. console.log("b is " + b)
  10. export let a = b + 1;
  11. /*
  12. before import a
  13. a is undefined
  14. before import b
  15. b is NAN
  16. */

这里的顺序也很简单,

  1. 进入 index.mjs ,提升 import(‘./modB.mjs’) 语句至顶部,最先执行,进入 modB
  2. 提升 提升 import(‘./index.mjs’) 语句至顶部,发现已经执行过 index.mjs,此处将从 index 导入的内容当作 { },执行剩余代码。完毕后,执行栈返回 index.mjs
  3. 执行剩余代码

3.AMD

历史上,js一直没有模块(module)体系,无法将一个项目拆分成多个模块文件。针对这一情况,社区出现了一些统一的规范:CommonJs和AMD,前者是针对服务端的js,也就是nodejs,后者是针对浏览器的

  1. 两者的模块导入导出语法不同:commonjs是module.exports,exports导出,require导入;ES6则是export导出,import导入。
  2. commonjs是运行时加载模块,ES6是在静态编译期间就确定模块的依赖。
  3. ES6在编译期间会将所有import提升到顶部,commonjs不会提升require。
  4. commonjs导出的是一个值拷贝,会对加载结果进行缓存,一旦内部再修改这个值,则不会同步到外部。ES6是导出的一个引用,内部修改可以同步到外部。
  5. 两者的循环导入的实现原理不同,commonjs是当模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。ES6 模块是动态引用,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
  6. commonjs中顶层的this指向这个模块本身,而ES6中顶层this指向undefined。