一、CommonJs
1、本质
CommonJs是定义模块,提供通用模块的组织方式
2、模块导出
关键字 module.exports 、exports
//counter.js
exports.count = 1;
module.exports.count2 = 2;
3、模块导入
关键词 require
//main.js
const counter = require('./counter.js')
console.log(counter.count); //1
console.log(counter.count2); //2
4、CommonJs对基本数据类型
属于复制,会被模块缓存,同时,在另一个模块可以对模块输出的变量重新赋值
//aa.js
exports.aa = 'aa';
// main.js
const aa = require('./aa')
aa.aa = 'aaa';
const bb = require('./aa')
console.log(bb.aa); //aaa
console.log(aa.aa); //aaa
5、CommonJs对复杂数据类型
属于浅拷贝,两个模块引用的同一个内存空间,对该模块做修改会影响另一个模块
//a.js
module.exports = {
foo: 2
};
//main.js
const a = require('./a');
const b = require('./a');
console.log(a.foo); //2
console.log(b.foo); //2
a.foo = 'a3';
console.log(a.foo); //a3
console.log(b.foo); //a3 a模块改变,b模块也会改变
6、CommonJs的循环引用和缓存
//b.js
const a = require('./a')
console.log('in b, a.a1 = %j, a.a2 = %j', a.a1, a.a2);
//a.js
exports.a1 = true;
const b = require('./b')
console.log('in a, b.done = %j', b.done);
exports.a2 = true;
//main.js
const a = require('./a')
console.log('in main, a.a1 = %j, a.a2 = %j', a.a1, a.a2);
//in b, a.a1 = true, a.a2 = undefined
// in a, b.done = undefined
// in main, a.a1 = true, a.a2 = true
实际在模块a还没有执行之前就已经创建好了Module实例写入缓存中,此时代码没有执行,exports是一个空对象
'd:\kjj_project\面试\js\11commonJs和Es6区别\测试\a.js':
Module {
exports: {},
//...
}
}
a.js 中的代码exports.a1 = true;修改了module.exports上的a1为true,这个时候a2的代码还没有执行
'd:\kjj_project\面试\js\11commonJs和Es6区别\测试\a.js':
Module {
exports: {
a1: true
}
//...
}
}
进入到b.js,require a.js 发现缓存已经存在了,直接获取a模块上的exports,打印a1,a2分别是true,undefined
运行完b.js 继续a模块的代码,exports.a2 = true; 又往 exports 对象上增加了a2属性,此时 module a 的 export对象 a1, a2 均为 true。
exports: {
a1: true,
a2: true
}
再回到 main 模块,由于 require('./a.js') 得到的是 Module a exports 对象的引用,这时候打印 a1, a2 就都为 true。
6、小结
CommonJs模块加载的过程是同步阻塞性的加载。在模块运行前就已经写入了cache,同一个模块被多次require只会执行一次,重复的require得到的是相同的exports的引用
二、Es6
1、查找、下载、解析、构建所有模块实例
Es6在程序开始会先根据模块关系找到所有模块,生成一个无环关系图,这个方式天然的避免了循环引用的问题,当然也有模块加载缓存,重复引入import,实际上只执行一次
在内存中腾出空间给即将export的内容,使用import和export指向这些内容,这个过程也叫连接
2、CommonJs
//counter
let count = 1
function increment() {
count++
}
module.exports = {
count,
increment
}
//main.js
const counter = require('./counter')
counter.increment(); // 修改的是模块内基础数据类型的变量,不会改变导出的值
console.log(counter.count); // 1
3、Es6
//counter.js
export let count = 1;
export function increment() {
count++;
}
//main.js
import { increment, count } from "./counter.js";
increment();
console.log(count); //2
从上面的对比中可以看出,从结果上看使用 ES6 模块的写法,当 export 的变量被修改时,会影响 import 的结果。
4、区别
require会将完整的exports对象引入,import可以只引入必要的内容
import没有找到export的变量,在执行前就会报错,而Common.js在运行时才会报错
commonJS输出是值得拷贝 ES6模块输出是值得引用
commonJS 是运行时加载, ES6模式是编译时输出接口
commonJS require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段
思考💡:开发中为什么CommonJs和Es6模块可以混用?
原因是在开发中写的Es6模块都会被打包工具处理成CommonJs模块