乐趣区

关于前端:聊聊CommonJS与ES6-Module的使用与区别

学了 JS 并且用过 Node.js 后,对模块化应该是有所理解和应用了,那么肯定见过以下两种模块导入导出的形式

第一种: ES6 Module

// B.js
function show() {console.log('show 办法被执行')
}

export default show

// A.js
import show from './B.js'
show()  // show 办法被执行

第二种: CommonJS

// B.js
function show() {console.log('show 办法被执行')
}

module.exports = {show}

// A.js
const bModule = require('./B.js')

bModule.show()  // show 办法被执行

以上两种导入导出办法波及到了两种模块标准,别离是ES6 ModuleCommonJS

本文就来聊聊这两者之间的具体应用与区别

1. CommonJS

CommonJS 是 2009 年由 JavaScript 社区提出的蕴含了模块化的一个规范,起初被 Node.js 所采纳并实现,也就是说咱们在 Node.js 中用到的模块导入导出都是按照 CommonJS 规范来实现的

1.1 导出

咱们能够把一个文件看成一个模块,每个模块之间是相互独立的,即不会相互影响。当须要应用到某个模块时,只需在文件中将指标模块导入即可

要想被其它模块导入首先须要导出须要向外裸露的变量或办法,在 CommonJS 中导出的语法有以下两种形式

// B.js
// 定义了函数 show
function show() {console.log('show 办法被调用')
}

// 定义了变量 count
let count = 3

/*--------------  导出办法  --------------*/

// 第一种
module.exports = {
    show,
    count
}

// 第二种
exports.show = show
exports.count = count

上述代码中,两种导出形式是等价的。

第一种导出形式是将须要导出的函数或变量存储到 module.exports 外面,其中 module.exports 本来是一个空对象

第二种导出形式中,exports 在外部其实是指向了 module.exports,所以当咱们执行 exports. 变量exports. 函数 时,其实就相当于把变量或函数存储到 module.exports

留神: 这里要特别强调的是,在应用第二种导出形式时,不能对 exports 进行从新赋值,否则就将 module.exports 间接全副笼罩了

1.2 导入

再来看一下 CommonJS 的导入语法

// A.js
const bModule = require('./B.js')

console.log(bModule.count)  // 3

bModule.show()  // show 办法被调用

从上述代码中能够看到,CommonJS 是通过 require 办法来导入模块的,其参数为模块文件门路,要特地留神的是,咱们导入模块后接管到的其实是一个对象,也就是 module.exports 的值,咱们能从该对象中获取到所需的变量或函数

另外,比拟特地的是,require 办法还能够接管一个表达式作为参数,代码如下

let fileName = 'B.js'
const bModule = require('./' + fileName)

因而,咱们是能够动静的扭转并决定模块的加载导入门路的

2. ES6 Module

如题目名写的,该模块规范是在 ES6 时才被提出的,尔后 JS 才具备了模块化这一个性

2.1 导出

在 ES6 Module 中,导出用到了关键字 export,导出的形式也大抵分为两种,别离是 命名导出 默认导出

第一种: 命名导出

// B.js
/*--------  单个变量或函数导出  ----------*/
export function show() { console.log('show 办法被调用') }

export let count = 3

/*--------  批量导出  ----------*/
function show() { console.log('show 办法被调用') }

let count = 3

export {show, count}

上述代码分了两种状况,且这两种写法是等价的

第一种是单个的变量或函数导出,只须要间接在结尾应用 export 关键字即可;

第二种状况是批量地把多个变量或函数导出,只须要把它们贮存到一个对象中即可

第二种: 默认导出

// B.js
function show() { console.log('show 办法被调用') }

// 命名导出变量 count
export let count = 3

// 默认导出函数 show
export default show

默认导出是在 export 关键词前面再跟上一个 default 示意导出的该变量或函数是匿名的

留神: 一个模块只能默认导出一次,否则就会报错,具体起因会在前面解说

2.2 导入

ES6 Module 的导入用到的关键字是 import,具体代码如下

// A.js
import {show, count} from './B.js'

show()   // show 办法被调用

console.log(count)  // 3

ES6 Module 的导入须要用一对 {} 大括号来接管咱们须要导入的办法或函数

留神: 大括号中的变量或函数名必须与导出时的名称截然不同

那么如果咱们想批改导入的变量或函数的名称,能够通过 as 关键词来命名,代码如下

// A.js
import {show as print, count as number} from './B.js'

print()   // show 办法被调用

console.log(number)  // 3

如果咱们要想将所有的变量或函数都导入,能够通过 * 来整体导入,代码如下

import * as bModule from './B.js'

bModule.show()  // show 办法被调用

console.log(bModule.count)  // 3

* 示意全副的意思,咱们将其全副导入,并赋值给 bModule,这样咱们就能够通过 bModule 获取想要的变量或对象了

以上所说的都是针对命名导出的变量或函数,那么如何导入一个默认导出的变量或函数呢?

// 将通过 export default 导出的变量导入
import print from './B.js'

print()  // show 办法被调用

命名导出的变量都是通过 {} 来接管的,那么去掉 {},接管的就是默认导出的变量了,因为导出的变量是匿名的,因而咱们能够随便地起个变量名用于接管

补充: 这里特地提一下,与 CommonJS 不同,ES6 Module 的导入文件门路是不反对表达式的

3. CommonJS 与 ES6 Module 的区别

这两者的次要区别次要有以下两点:

  1. 对于模块的依赖,CommonJS 是 动静的 ,ES6 Module 是 动态的
  2. CommonJS 导入的是值的 拷贝 ,ES6 Module 导入的是值的 援用

3.1 区别一

对于模块的依赖,何为 动静 ?何为 动态

动静是指对于模块的依赖关系建设在代码执行阶段;
动态是指对于模块的依赖关系建设在代码编译阶段;

上文提到,CommonJS 导入时,require 的门路参数是反对表达式的,例如

// A.js
let fileName = 'example.js'
const bModule = require('./' + fileName)

因为该门路在代码执行时是能够动静扭转的,所以如果在代码编译阶段就建设各个模块的依赖关系,那么肯定是不精确的,只有在代码运行了当前,才能够真正确认模块的依赖关系,因而说 CommonJS 是动静的。

那么当初你也应该也晓得为什么 ES6 Module 是动态的了吧

3.2 区别二

为了验证这一点,我筹备用实例来演示一下

首先来验证 CommonJS,代码如下

// B.js
let count = 3

function change() {
    count ++    // 变量 count + 1
    console.log('原 count 值为:', count);  // 打印 B.js 模块中 count 的值
}

module.exports = {
    count,
    change
}

// A.js
let count = require('./B.js').count 
let change = require('./B.js').change

console.log('扭转前:', count);   
change()     // 调用模块 B.js 中的 change 办法,将原来的 count + 1
console.log('扭转后:', count); 

// 运行 A.js 文件的后果
扭转前:3
原 count 值为:4
扭转后:3

在上述代码中咱们能够看到,在 A.js 文件中导入了 B.js 文件中的变量 count 和 函数 change,因为导入的 count 只是对原有值的一个拷贝,因而只管咱们调用了函数 change 扭转了 B.js 文件中变量 count 的值,也不会影响到 A.js 文件中的变量 count

依据这个后果得出结论:CommonJS 导入的变量是对原值的拷贝


接下来再来验证一下 ES6 Module,代码如下

// B.js
let count = 3

function change() {
    count ++        // 变量 count + 1
    console.log(count);   // 打印 B.js 模块中 count 的值
}

export {count, change}

// A.js
import {count, change} from './B.js';

console.log('扭转前:',count);

change()         // 调用模块 B.js 中的 change 办法,将原来的 count + 1

console.log('扭转后:', count);

// 运行 A.js 文件的后果
扭转前:3
原 count 值为:4
扭转后:4

相比拟于 CommonJS 的后果,ES6 Module 导入的变量 count 随着原值的扭转而扭转了

依据这个后果得出结论:ES6 Module 导入的变量是对原值的援用

4. The end

本篇文章就到此结束啦,对于 CommonJS 和 ES6 Module,还有一些本文未提及的常识,还是心愿大家深刻理解

关注公众号:前端印象,每日分享一个高质量前端技术文,还能支付丰盛前端材料

退出移动版