// base.jsvar count = 0;setTimeout(() => {console.log("base.count", ++count);}, 500)module.exports.count = count;// commonjs.jsconst { count } = require('./base');setTimeout(() => {console.log("count is" + count + 'in commonjs'); // 0}, 1000)// base1.jsvar count = 0;setTimeout(() => {console.log("base1.count", ++count); // 1}, 500)exports var count = count;// es6.jsimport { count } from'./base1';setTimeout(() => {console.log("count is" + count + 'in es6'); // 1}, 1000)
错误❌因为commonjs是值的拷贝,es6的import是值的引用
commonjs是值的拷贝这个有问题,应该是在引入的时候,对该module的exports进行了一次浅拷贝,因为导出的count不会变,但是导出的nums.count会改变。
es6的import也不好直接说是引用,更倾向于是链接指向。
CommonJs
加载原理
commonjs规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,他的exports属性是对外接口。加载某个模块其实本质就是加载他的 module.exports 属性。
//example.jslet x = 5;let addX = function (value) {return value + x;};module.exports.x = x;module.exports.addX = addX;//require.jsvar example = require('./example.js');console.log(example.x); // 5console.log(example.addX(1)); // 6
特点
- 所有代码运行在模块作用域,不会污染全局作用域。
- 模块可以加载多次但是只会在第一次加载的时候运行,然后结果就会被缓存,之后再加载会直接读缓存,想要重新运行就要清除缓存。
- 模块的加载,按照其在代码中出现的顺序加载。
module对象
node内部提供一个module构建函数。所有模块都是Module的实例。
function Module(id,parent){this.id = id;this.exports = {}// ....}
每个模块内部都有一个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输出。(反正他们是一样的)
module.exports = function (x){ console.log(x);};
如果你觉得,exports与module.exports之间的区别很难分清,一个简单的处理方法,就是放弃使用exports,只使用module.exports。
模块的缓存
第一次加载模块时,模块代码运行,之后node会缓存该模块。之后再加载该模块,就直接从缓存中读取该模块的module.exports属性。
如果想要多次执行某模块,可以让该模块输出一个函数,然后每次require这个模块的时候重新执行一下输出的函数。
所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。
// 删除指定模块的缓存delete require.cache[moduleName]// 删除所有模块缓存Object.keys(require.cache).forEach(function(key) {delete require.cache[key];})
注意,缓存是根据绝对路径识别模块的,如果同样的模块名,但是保存在不同的路径,require命令还是会重新加载该模块。
ES6模块
es6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。 es的export输出的是代码
// CommonJS模块let { stat, exists, readFile } = require('fs');// 实质是整体加载fs模块,生成一个_fs对象,然后从该对象上获取3个方法,这种加载叫运行时加载。// ES6模块import { stat, exists, readFile } from'fs';// 而es6模块并不是对象,而是export显式指定的输出 ***代码*** ,再通过import命令输入。// 上述代码实际只是从fs模块加载三个方法,其他方法不加载。
由此可见,es6是在编译时就完成了模块加载,效率要比其他加载方式高。当然这也就导致没办法加载es6的模块本身,因为他不是对象。
es6主要由两个命令构成:export 和 import。 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都不是接口。<a name="nc496"></a>#### import命令- **import命令输入的变量都是只读的**- **import命令具有提升的效果**- **import是一个单例模式**- **import是静态执行的,所以不能使用表达式和变量**```javascript// 验证只读import {a} from'./xxx.js'a = {}; // Syntax Error : 'a' is read-only;// 验证提升foo();import { foo } from'my_module';// 验证单例模式import { foo } from'my_module';import { bar } from'my_module';// 等同于import { foo, bar } from'my_module';// 验证静态执行import { 'f' + 'oo' } from'my_module';// 报错letmodule = 'my_module';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()函数之中。
// main.js 定义一个模块define(function (){var add = function (x,y){return x+y;};return {add: add};});//如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。例如:define(['myLib'], function(myLib){function foo(){myLib.doSomething();}return {foo : foo};});
// 引入一个模块,第一个参数是模块名,第二个参数是引入后的回调require(['math'], function (math){alert(math.add(1,1));});
