CommonJS
是由JavaScript
社区在2009提出的包含模块、文件、IO、控制台在内的一系列标准。在Node.js
的实现中采用CommonJS
标准的一部分,并在它的基础上进行了一些调整。我们听说的CommonJS
模块和Node.js
中的实现并不完全一样,现在一般谈到CommonJS
其实就是Node.js
的版本,而非原始版本。
CommonJS
中规定每个文件就是一个文件模块,使用require
函数引入使用模块。
// add.js
console.log('current file add.js')
// main.js
require('./add.js') //current file add.js
在CommonJS
中导出是一个模块向外唯一暴露自身的方式。在CommonJS
中通过module.exports
导出模块内容。module
是CommonJS
内部定义的一个对象,存放模块信息,可以看做每个模块的顶部都有一个module
对象定义,这个对象有个exports
属性。模块内除了module
定义外还会声明exports
定义,exports
是指向module.exports
的。所以需要注意,对exports
赋值时module.exports
并不会更改。
// add.js
// 模块导出可以看做
// var module = { exports: {} }
// var exports = module.exports
module.exports = {
name: 'module.exports add'
}
// 直接更改相当于对exports变量的更改,并不会真实更改到module.exports
exports = {
name: 'exports add'
}
// main.js
const add = require('./add.js')
console.log(add.name) // module.exports add
在CommonJS
中使用require
方法进行模块导入。require
导入的模块只有第一次加载才会执行模块的内容,后续都是获取到第一次执行模块内容是生成的module.exports
。require
函数可以接受表达式,也就是动态的。
// add.js
console.log('run add.js')
module.exports = (a, b) => {
return a + b
}
// demo1.js
const add = require('./add.js')
console.log(add(1, 2))
// demo2.js
const add = require('./add.js')
console.log(add(2, 2))
// main.js
const moduleNames = ['./demo1.js', './demo2.js']
/**
* run add.js
* 3
* 4
**/
moduleNames.forEach(require)
在2015年6月,TC39
标准委员会正式发布了ES6
,从此JavaScipt
语言具备模块这一特征.
ES6 Module
也是将每个文件作为一个单独的模块。import
和export
作为保留关键字在ES6
版本中也加入了进来。当使用ES6
模块时会自动采用严格模式,在ES5
使用use strict
开启。
// add.js
console.log('current file add.js')
// main.js
import 'add.js' //current file add.js
ES6 Module
使用export
关键字导出模块,可以使用命名导出和默认导出两种方式。命名导出有凉中方式,第一种是将声明和导出一起执行,第二种是先声明再用同一个export
导出。使用命名导出时可以使用as
关键字对变量重命名。默认导出只能有一个。
// demoA.js
export const name = 'demoA'
export const add = (a, b) => a + b
// demoB.js
const name = 'demoB'
const add = (a, b) => a + b
export { name, add as addB }
export default add
ES6 Module
使用import
关键字导入模块。与require
类似,import
导入的模块只有第一次加载才会执行模块的内容。在import
关键字后面使用大括号将要导入的变量名包裹起来,与导出类似可以使用as
关键字对变量重命名。可以使用import * as <myModule>
来讲模块的所有变量导入到<myModule>
对象中。对于默认导出可以使用import
后面跟变量名的方式引入,也可以使用大括号包裹,导出在default
的变量中。另外导入可以多种方式并用,用逗号隔开。
// demo1.js
console.log('run demo1.js')
export const name = 'demo1'
export default (a, b) => {
return a + b
}
// demo2
import './demo1'
export const name = 'demo2'
export default (a, b) => {
return a - b
}
// main.js
import { name, default as add } from './demo1.js'
import diff, { name as diffName } from './demo2.js'
ES6 Module
还支持复合写法,比如将一个模块导入后马上导出,一般是专门用在模块的入口文件。
// a.js
export const add = (a, b) => a + b
export const name = 'a'
export default diff = (a, b) => a - b
// main.js
export { add, name, default as diff } from './a.js'
上面我们介绍的CommonJS
和ES6 Module
两种形式的模块我们开发中经常会使用,这里我们对比一下它们各自的特征。
CommonJS
与ES6 Module
最大的区别在于前者对模块的依赖是动态的,后者是静态的。动态是指模块依赖关系的建立是在代码运行时建立的。静态是指模块依赖关系是在编译节点建立的。
CommonJS
中require
引用的路径可以动态指定,支持表达式,可以通过if
语句来判断是否加载某个模块。module.exports
也是个对象,可以随时动态的添加导出变量。所以在代码执行前都是没办法确定依赖关系的。ES6 Module
的import
和module
导入导出都是声明式的,不支持表达式,而且导入导出都必须在模块的顶层作用域(无法放置到if
语句中)。所以ES6 Module
在编译阶段就能分析出依赖关系。ES6 Module
相对与CommonJS
来说有下面2点优点
ES6 Module
的静态模块解构有助于确认模块之间传递的值和接口的正确性 在导入一个模块时,CommonJS
是将导出值进行一份拷贝赋值给变量;而在ES6 Module
中则是值的动态映射,并且这个映射是无法更改的。值拷贝意味着当引用模块内的变量变化时,引用者使用的变量可能是旧的,并且可能是错的,而动态映射则没这个问题,引用模块抛出的永远是模块内变量的映射而已。比如看下方CommonJS
和ES6 Module
的两个例子
// demo.js
var count = 0;
module.exports = {
count: count,
add: function(a, b) {
count += 1
return a + b
}
}
// main.js
const count = require('./demo').count
const add = require('./demo').add
console.log(count) // 0
add(1, 1)
console.log(count) // 0
count += 1
console.log(count) // 拷贝值可更改
// demo.js
export let count = 0;
export const add = () => {
count += 1
return a + b
}
// main.js
import { count, add } from './demo'
console.log(count) // 0
add(1, 1)
console.log(count) // 1
// count += 1 SyntaxError
完结撒花。
©2021 - bill-lai 的小站 -站点源码