js|CommonJs和Es6模块区别

135 阅读3分钟

一、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.jsrequire a.js 发现缓存已经存在了,直接获取a模块上的exports,打印a1,a2分别是true,undefined

运行完b.js 继续a模块的代码,exports.a2 = true; 又往 exports 对象上增加了a2属性,此时 module aexport对象 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模块

参考👀

CommonJS模块与ES6模块的区别

CommonJS 和 ES6 Module 究竟有什么区别?