【转】
遵循的模块化规范不一样
模块化规范:即为 JavaScript 提供一种模块编写、模块依赖和模块运行的方案。谁让最初的 JavaScript 是那么的裸奔呢——全局变量就是它的模块化规范。
require/exports 出生在野生规范当中,什么叫做野生规范?即这些规范是 JavaScript 社区中的开发者自己草拟的规则,得到了大家的承认或者广泛的应用。比如 CommonJS、AMD、CMD 等等。import/export 则是名门正派。TC39 制定的新的 ECMAScript 版本,即 ES6(ES2015)中包含进来。
出现的时间不同
require/exports 相关的规范由于野生性质,在 2010 年前后出生。AMD、CMD 相对命比较短,到 2014 年基本上就摇摇欲坠了。一开始大家还比较喜欢在浏览器上采用这种异步小模块的加载方式,但并不是银弹。随着 Node.js 流行和 Browsersify 的兴起,运行时异步加载逐渐被构建时模块合并分块所替代。Wrapper 函数再也不需要了。 2014 年 Webpack 还是新玩意,现在已经是前端必备神器了。
Browsersify、Webpack 一开始的目的就是打包 CommonJS 模块。
CommonJS 作为 Node.js 的规范,一直沿用至今。由于 npm 上 CommonJS 的类库众多,以及 CommonJS 和 ES6 之间的差异,Node.js(这里不太准确) 无法直接兼容 ES6。所以现阶段 require/exports 仍然是必要且必须的。出自 ES6 的 import/export 相对就晚了许多。被大家所熟知和使用也是 2015 年之后的事了。 这其实要感谢 babel(原来项目名叫做 6to5,后更名为 babel) 这个神一般的项目。由于有了 babel 将还未被宿主环境(各浏览器、Node.js)直接支持的 ES6 Module 编译为 ES5 的 CommonJS —— 也就是 require/exports 这种写法 —— Webpack 插上 babel-loader 这个翅膀才开始高飞,大家也才可以称 " 我在使用 ES6! "
这也就是为什么前面说 require/exports 是必要且必须的。因为事实是,目前你编写的 import/export 最终都是编译为 require/exports 来执行的。
require/exports 和 import/export 形式不一样
require/exports 的用法只有以下三种简单的写法:
const fs = require('fs')exports.fs = fsmodule.exports = fs
而 import/export 的写法就多种多样:
import fs from 'fs'import {default as fs} from 'fs'import * as fs from 'fs'import {readFile} from 'fs'import {readFile as read} from 'fs'import fs, {readFile} from 'fs'export default fsexport const fsexport function readFileexport {readFile, read}export * from 'fs'
require/exports 和 import/export 本质上的差别
形式上看起来五花八门,但本质上:
CommonJS 还是 ES6 Module 输出都可以看成是一个具备多个属性或者方法的对象;
default 是 ES6 Module 所独有的关键字,export default fs 输出默认的接口对象,import fs from 'fs' 可直接导入这个对象;
ES6 Module 中导入模块的属性或者方法是强绑定的,包括基础类型;而 CommonJS 则是普通的值传递或者引用传递。
1、2 相对比较好理解,3 需要看个例子:
// counter.jsexports.count=0setTimeout(function(){console.log('increase count to',++exports.count,'in counter.js after 500ms')},500)// commonjs.jsconst{count}=require('./counter')setTimeout(function(){console.log('read count after 1000ms in commonjs is',count)},1000)//es6.jsimport{count}from'./counter'setTimeout(function(){console.log('read count after 1000ms in es6 is',count)},1000)
分别运行 commonjs.js 和 es6.js:
➜testnode commonjs.jsincrease count to1in counter.js after 500msreadcount after 1000ms in commonjs is 0➜testbabel-node es6.jsincrease count to1in counter.js after 500msreadcount after 1000ms in es6 is 1
作者:寸志
链接:https://www.zhihu.com/question/56820346/answer/150724784
来源:知乎
【转】commonjs模块与es6模块的区别
到目前为止,已经实习了3个月的时间了。最近在面试,在面试题里面有题目涉及到模块循环加载的知识。趁着这个机会,将commonjs模块与es6模块之间一些重要的的区别做个总结。语法上有什么区别就不具体说了,主要谈谈引用的区别。
转载请注明出处:commonjs模块与es6模块的区别
commonjs
对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
当使用require命令加载某个模块时,就会运行整个模块的代码。
当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,commonjs模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
ES6模块
es6模块中的值属于【动态只读引用】。
对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。
上面说了一些重要区别。现在举一些例子来说明每一点吧
commonjs
对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
// b.js
let count = 1
let plusCount = () => {
count++
}
setTimeout(() => {
console.log('b.js-1', count)
}, 1000)
module.exports = {
count,
plusCount
}
// a.js
let mod = require('./b.js')
console.log('a.js-1', mod.count)
mod.plusCount()
console.log('a.js-2', mod.count)
setTimeout(() => {
mod.count = 3
console.log('a.js-3', mod.count)
}, 2000)
node a.js
a.js-1 1
a.js-2 1
b.js-1 2 // 1秒后
a.js-3 3 // 2秒后
以上代码可以看出,b模块export的count变量,是一个复制行为。在plusCount方法调用之后,a模块中的count不受影响。同时,可以在b模块中更改a模块中的值。如果希望能够同步代码,可以export出去一个getter。
// 其他代码相同
module.exports = {
get count () {
return count
},
plusCount
}
node a.js
a.js-1 1
a.js-2 1
b.js-1 2 // 1秒后
a.js-3 2 // 2秒后, 由于没有定义setter,因此无法对值进行设置。所以还是返回2
对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
// b.js
let obj = {
count: 1
}
let plusCount = () => {
obj.count++
}
setTimeout(() => {
console.log('b.js-1', obj.count)
}, 1000)
setTimeout(() => {
console.log('b.js-2', obj.count)
}, 3000)
module.exports = {
obj,
plusCount
}
// a.js
var mod = require('./b.js')
console.log('a.js-1', mod.obj.count)
mod.plusCount()
console.log('a.js-2', mod.obj.count)
setTimeout(() => {
mod.obj.count = 3
console.log('a.js-3', mod.obj.count)
}, 2000)
node a.js
a.js-1 1
a.js-2 2
b.js-1 2
a.js-3 3
b.js-2 3
以上代码可以看出,对于对象来说属于浅拷贝。当执行a模块时,首先打印obj.count的值为1,然后通过plusCount方法,再次打印时为2。接着在a模块修改count的值为3,此时在b模块的值也为3。
3.当使用require命令加载某个模块时,就会运行整个模块的代码。
4.当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,commonjs模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
5.循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
3, 4, 5可以使用同一个例子说明
// b.js
exports.done = false
let a = require('./a.js')
console.log('b.js-1', a.done)
exports.done = true
console.log('b.js-2', '执行完毕')
// a.js
exports.done = false
let b = require('./b.js')
console.log('a.js-1', b.done)
exports.done = true
console.log('a.js-2', '执行完毕')
// c.js
let a = require('./a.js')
let b = require('./b.js')
console.log('c.js-1', '执行完毕', a.done, b.done)
node c.js
b.js-1 false
b.js-2 执行完毕
a.js-1 true
a.js-2 执行完毕
c.js-1 执行完毕 true true
仔细说明一下整个过程。
在Node.js中执行c模块。此时遇到require关键字,执行a.js中所有代码。
在a模块中exports之后,通过require引入了b模块,执行b模块的代码。
在b模块中exports之后,又require引入了a模块,此时执行a模块的代码。
a模块只执行exports.done = false这条语句。
回到b模块,打印b.js-1, exports, b.js-2。b模块执行完毕。
回到a模块,接着打印a.js-1, exports, b.js-2。a模块执行完毕
回到c模块,接着执行require,需要引入b模块。由于在a模块中已经引入过了,所以直接就可以输出值了。
结束。
从以上结果和分析过程可以看出,当遇到require命令时,会执行对应的模块代码。当循环引用时,有可能只输出某模块代码的一部分。当引用同一个模块时,不会再次加载,而是获取缓存。
ES6模块
es6模块中的值属于【动态只读引用】。只说明一下复杂数据类型。
对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
// b.js
export let counter = {
count: 1
}
setTimeout(() => {
console.log('b.js-1', counter.count)
}, 1000)
// a.js
import { counter } from './b.js'
counter = {}
console.log('a.js-1', counter)
// Syntax Error: "counter" is read-only
虽然不能将counter重新赋值一个新的对象,但是可以给对象添加属性和方法。此时不会报错。这种行为类型与关键字const的用法。
// a.js
import { counter } from './b.js'
counter.count++
console.log(counter)
// 2
循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。
// b.js
import {foo} from './a.js';
export function bar() {
console.log('bar');
if (Math.random() > 0.5) {
foo();
}
}
// a.js
import {bar} from './b.js';
export function foo() {
console.log('foo');
bar();
console.log('执行完毕');
}
foo();
babel-node a.js
foo
bar
执行完毕
// 执行结果也有可能是
foo
bar
foo
bar
执行完毕
执行完毕
由于在两个模块之间都存在引用。因此能够正常执行。
以上以上。对es6 module和commonjs module有不了解的同学可以参考一下以下的文章
来源:http://www.cnblogs.com/unclekeith/p/7679503.html