详谈commonjs模块与es6模块的区别
到目前为止,已经实习了3个月的时间了。最近在面试,在面试题里面有题目涉及到模块循环加载的知识。趁着这个机会,将commonjs模块与es6模块之间一些重要的的区别做个总结。语法上有什么区别就不具体说了,主要谈谈引用的区别。
commonjs
对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
当使用require命令加载某个模块时,就会运行整个模块的代码。
当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,commonjs模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
ES6模块
es6模块中的值属于【动态只读引用】。
对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
循环加载时,
上面说了一些重要区别。现在举一些例子来说明每一点吧
commonjs
对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
//b.js
letcount=1
letplusCount=()=>{
count++
}
setTimeout(()=>{
console.log('b.js-1',count)
},1000)
module.exports={
count,
plusCount
}
//a.js
letmod=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)
nodea.js
a.js-11
a.js-21
b.js-12//1秒后
a.js-33//2秒后
以上代码可以看出,b模块export的count变量,是一个复制行为。在plusCount方法调用之后,a模块中的count不受影响。同时,可以在b模块中更改a模块中的值。如果希望能够同步代码,可以export出去一个getter。
//其他代码相同
module.exports={
getcount(){
returncount
},
plusCount
}
nodea.js
a.js-11
a.js-21
b.js-12//1秒后
a.js-32//2秒后,由于没有定义setter,因此无法对值进行设置。所以还是返回2
对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
//b.js
letobj={
count:1
}
letplusCount=()=>{
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
varmod=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)
nodea.js
a.js-11
a.js-22
b.js-12
a.js-33
b.js-23
以上代码可以看出,对于对象来说属于浅拷贝。当执行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
leta=require('./a.js')
console.log('b.js-1',a.done)
exports.done=true
console.log('b.js-2','执行完毕')
//a.js
exports.done=false
letb=require('./b.js')
console.log('a.js-1',b.done)
exports.done=true
console.log('a.js-2','执行完毕')
//c.js
leta=require('./a.js')
letb=require('./b.js')
console.log('c.js-1','执行完毕',a.done,b.done)
nodec.js
b.js-1false
b.js-2执行完毕
a.js-1true
a.js-2执行完毕
c.js-1执行完毕truetrue
仔细说明一下整个过程。
在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
exportletcounter={
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)
//SyntaxError:"counter"isread-only
虽然不能将counter重新赋值一个新的对象,但是可以给对象添加属性和方法。此时不会报错。这种行为类型与关键字const的用法。
//a.js
import{counter}from'./b.js'
counter.count++
console.log(counter)
//2
循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。
//b.js
import{foo}from'./a.js';
exportfunctionbar(){
console.log('bar');
if(Math.random()>0.5){
foo();
}
}
//a.js
import{bar}from'./b.js';
exportfunctionfoo(){
console.log('foo');
bar();
console.log('执行完毕');
}
foo();
nodea.js
foo
bar
执行完毕
//执行结果也有可能是
foo
bar
foo
bar
执行完毕
执行完毕
由于在两个模块之间都存在引用。因此能够正常执行。
以上这篇详谈commonjs模块与es6模块的区别就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。