1. // base.js
  2. var count = 0;
  3. setTimeout(() => {
  4. console.log("base.count", ++count);
  5. }, 500)
  6. module.exports.count = count;
  7. // commonjs.js
  8. const { count } = require('./base');
  9. setTimeout(() => {
  10. console.log("count is" + count + 'in commonjs'); // 0
  11. }, 1000)
  12. // base1.js
  13. var count = 0;
  14. setTimeout(() => {
  15. console.log("base1.count", ++count); // 1
  16. }, 500)
  17. exports var count = count;
  18. // es6.js
  19. import { count } from'./base1';
  20. setTimeout(() => {
  21. console.log("count is" + count + 'in es6'); // 1
  22. }, 1000)

错误❌因为commonjs是值的拷贝,es6的import是值的引用
commonjs是值的拷贝这个有问题,应该是在引入的时候,对该module的exports进行了一次浅拷贝,因为导出的count不会变,但是导出的nums.count会改变。
es6的import也不好直接说是引用,更倾向于是链接指向。

CommonJs

加载原理

commonjs规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,他的exports属性是对外接口。加载某个模块其实本质就是加载他的 module.exports 属性。

  1. //example.js
  2. let x = 5;
  3. let addX = function (value) {
  4. return value + x;
  5. };
  6. module.exports.x = x;
  7. module.exports.addX = addX;
  8. //require.js
  9. var example = require('./example.js');
  10. console.log(example.x); // 5
  11. console.log(example.addX(1)); // 6

特点

  • 所有代码运行在模块作用域,不会污染全局作用域。
  • 模块可以加载多次但是只会在第一次加载的时候运行,然后结果就会被缓存,之后再加载会直接读缓存,想要重新运行就要清除缓存。
  • 模块的加载,按照其在代码中出现的顺序加载。

module对象

node内部提供一个module构建函数。所有模块都是Module的实例。

  1. function Module(id,parent){
  2. this.id = id;
  3. this.exports = {}
  4. // ....
  5. }

每个模块内部都有一个module对象,代表当前模块。它有以下属性:

  • module.id 模块的标识符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,标识模块是否已经完成加载。
  • module.exports 表示模块对外输出的值。(require加载模块实际上就是读取module.exports变量)

为了方便,Node为每个模块提供一个exports变量,指向module.exports。等同于在每个模块头部都有一行: let exports = module.exports
所以开发时要注意,不能直接将exports变量指向一个值,因为这样就切断了exports和module.exports
的联系。这意味着,如果一个模块的对外接口,就是一个单一的值,最好不要使用exports输出,最好使用module.exports输出。(反正他们是一样的)

  1. module.exports = function (x){ console.log(x);};

如果你觉得,exports与module.exports之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。

模块的缓存

第一次加载模块时,模块代码运行,之后node会缓存该模块。之后再加载该模块,就直接从缓存中读取该模块的module.exports属性。
如果想要多次执行某模块,可以让该模块输出一个函数,然后每次require这个模块的时候重新执行一下输出的函数。
所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。

  1. // 删除指定模块的缓存
  2. delete require.cache[moduleName]
  3. // 删除所有模块缓存
  4. Object.keys(require.cache).forEach(function(key) {
  5. delete require.cache[key];
  6. })

注意,缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。

ES6模块

es6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。 es的export输出的是代码

  1. // CommonJS模块
  2. let { stat, exists, readFile } = require('fs');
  3. // 实质是整体加载fs模块,生成一个_fs对象,然后从该对象上获取3个方法,这种加载叫运行时加载。
  4. // ES6模块
  5. import { stat, exists, readFile } from'fs';
  6. // 而es6模块并不是对象,而是export显式指定的输出 ***代码*** ,再通过import命令输入。
  7. // 上述代码实际只是从fs模块加载三个方法,其他方法不加载。

由此可见,es6是在编译时就完成了模块加载,效率要比其他加载方式高。当然这也就导致没办法加载es6的模块本身,因为他不是对象。
es6主要由两个命令构成:exportimport。 export命令用于规定模块的对外接口。import用于输入其他模块提供的功能。

export命令

  • export必须与模块内部的变量建立一一对应关系。 ```javascript // 报错 export 1; // 报错 let m = 1; export m;

// 正确写法 // 第一种 let m = 1; export {m} export {n as m}

// 第二种 export let m=1

  1. 第一种错误直接导出的'1',是值;第二种是直接导出变量; 无论是变量还是1都不是接口。
  2. <a name="nc496"></a>
  3. #### import命令
  4. - **import命令输入的变量都是只读的**
  5. - **import命令具有提升的效果**
  6. - **import是一个单例模式**
  7. - **import是静态执行的,所以不能使用表达式和变量**
  8. ```javascript
  9. // 验证只读
  10. import {a} from'./xxx.js'
  11. a = {}; // Syntax Error : 'a' is read-only;
  12. // 验证提升
  13. foo();
  14. import { foo } from'my_module';
  15. // 验证单例模式
  16. import { foo } from'my_module';
  17. import { bar } from'my_module';
  18. // 等同于
  19. import { foo, bar } from'my_module';
  20. // 验证静态执行
  21. import { 'f' + 'oo' } from'my_module';// 报错
  22. letmodule = 'my_module';
  23. import { foo } frommodule;// 报错

commonjs和es6主要差异

CommonJs模块输出的是一个值的拷贝,ES6模块输出的是值的引用。
CommonJs模块是运行时加载,ES6模块是编译时输出接口。

AMD和commonjs的差异

在前面我们具体讲解了commonjs的加载原理和特点,我们也知道了commonjs必须要有 module、export和require。
但是浏览器上并没有对应的规范,所以commonjs的规范按理来说只是在node端使用的。

AMD

模块化这么好用,自然而然的我们就希望把它应用在客户端。但是由于一个重大的局限,commonjs的规范不能适用于客户端。在服务端,所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。而浏览器读取文件一般都是发送网络请求,同步读取的commonjs对于浏览器来说,就是一个稳定’假死‘机制。
因此,浏览器端的模块,不能采用”同步加载”,只能采用”异步加载”。这就是AMD规范诞生的背景。
require.js就是实现了AMD规范的库。

AMD模块的写法

require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。
块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

  1. // main.js 定义一个模块
  2. define(function (){
  3. var add = function (x,y){
  4. return x+y;
  5. };
  6. return {
  7. add: add
  8. };
  9. });
  10. //如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。例如:
  11. define(['myLib'], function(myLib){
  12. function foo(){
  13. myLib.doSomething();
  14. }
  15. return {
  16. foo : foo
  17. };
  18. });
  1. // 引入一个模块,第一个参数是模块名,第二个参数是引入后的回调
  2. require(['math'], function (math){
  3.     alert(math.add(1,1));
  4.   });

ADM中文版规范)