// base.js
var count = 0;
setTimeout(() => {
console.log("base.count", ++count);
}, 500)
module.exports.count = count;
// commonjs.js
const { count } = require('./base');
setTimeout(() => {
console.log("count is" + count + 'in commonjs'); // 0
}, 1000)
// base1.js
var count = 0;
setTimeout(() => {
console.log("base1.count", ++count); // 1
}, 500)
exports var count = count;
// es6.js
import { 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.js
let x = 5;
let addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
//require.js
var example = require('./example.js');
console.log(example.x); // 5
console.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));
});