Comments (8)
1. CommonJS
模块是运行时加载,ES6模块是编译时输出接口。
- ES6模块在编译时,就能确定模块的依赖关系,以及输入和输出的变量。ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
CommonJS
加载的是一个对象,该对象只有在脚本运行完才会生成。
2. CommonJS
模块输出的是一个值的拷贝,ES6模块输出的是值的引用。
- `CommonJS` 输出的是一个值的拷贝(注意基本数据类型/复杂数据类型)
- ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
CommonJS 模块输出的是值的拷贝。
模块输出的值是基本数据类型,模块内部的变化就影响不到这个值。
//name.js
let name = 'William';
setTimeout(() => { name = 'Yvette'; }, 300);
module.exports = name;
//index.js
const name = require('./name');
console.log(name); //William
//name.js 模块加载后,它的内部变化就影响不到 name
//name 是一个基本数据类型。将其复制出一份之后,二者之间互不影响。
setTimeout(() => console.log(name), 500); //William
模块输出的值是复杂数据类型
- 模块输出的是对象,属性值是简单数据类型时:
//name.js
let name = 'William';
setTimeout(() => { name = 'Yvette'; }, 300);
module.exports = { name };
//index.js
const { name } = require('./name');
console.log(name); //William
//name 是一个原始类型的值,会被缓存。
setTimeout(() => console.log(name), 500); //William
模块输出的是对象:
//name.js
let name = 'William';
let hobbies = ['coding'];
setTimeout(() => {
name = 'Yvette';
hobbies.push('reading');
}, 300);
module.exports = { name, hobbies };
//index.js
const { name, hobbies } = require('./name');
console.log(name); //William
console.log(hobbies); //['coding']
/*
* name 的值没有受到影响,因为 {name: name} 属性值 name 存的是个字符串
* 300ms后 name 变量重新赋值,但是不会影响 {name: name}
*
* hobbies 的值会被影响,因为 {hobbies: hobbies} 属性值 hobbies 中存的是
* 数组的堆内存地址,因此当 hobbies 对象的值被改变时,存在栈内存中的地址并
没有发生变化,因此 hoobies 对象值的改变会影响 {hobbies: hobbies}
* xx = { name, hobbies } 也因此改变 (复杂数据类型,拷贝的栈内存中存的地址)
*/
setTimeout(() => {
console.log(name);//William
console.log(hobbies);//['coding', 'reading']
}, 500);
ES6 模块的运行机制与 CommonJS
不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
//name.js
let name = 'William';
setTimeout(() => { name = 'Yvette'; hobbies.push('writing'); }, 300);
export { name };
export var hobbies = ['coding'];
//index.js
import { name, hobbies } from './name';
console.log(name, hobbies); //William ["coding"]
//name 和 hobbie 都会被模块内部的变化所影响
setTimeout(() => {
console.log(name, hobbies); //Yvette ["coding", "writing"]
}, 500); //Yvette
ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。因此上面的例子也很容易理解。
那么 export default
导出是什么情况呢?
//name.js
let name = 'William';
let hobbies = ['coding']
setTimeout(() => { name = 'Yvette'; hobbies.push('writing'); }, 300);
export default { name, hobbies };
//index.js
import info from './name';
console.log(info.name, info.hobbies); //William ["coding"]
//name 不会被模块内部的变化所影响
//hobbie 会被模块内部的变化所影响
setTimeout(() => {
console.log(info.name, info.hobbies); //William ["coding", "writing"]
}, 500); //Yvette
一起看一下为什么。
export default
可以理解为将变量赋值给 default
,最后导出 default
(仅是方便理解,不代表最终的实现,如果对这块感兴趣,可以阅读 webpack 编译出来的代码)。
基础类型变量 name
, 赋值给 default
之后,只读引用与 default
关联,此时原变量 name
的任何修改都与 default
无关。
复杂数据类型变量 hobbies
,赋值给 default
之后,只读引用与 default
关联,default
和 hobbies
中存储的是同一个对象的堆内存地址,当这个对象的值发生改变时,此时 default
的值也会发生变化。
from step-by-step.
照抄阮大神的书
- CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用
- CommonJS模块是运行时加载,ES6模块是编译时输出接口
第二个差异是因为CommonJS加载的是一个对象,即module.export属性,该对象只有在脚本运行结束时才会生成。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
下面重点解释第一个差异。
CommonJS模块输出的是值的复制,一旦输出这个值,模块内部的变化就影响不到这个值。
//lib.js 一个commonJS模块
var counter = 3
function incCounter() {
counter++
}
module.exports = {
counter : counter,
incCounter : incCounter,
}
//main.js 在这个函数里加载这个模块
var mod = require ('./lib')
console.log(mod.counter)
mod.incCounter()
console.log(mod.counter)
3
3
上面的代码说明,lib.js模块加载后,它的内部变化就影响不到输出的mod.counter 了。
这是因为mod.counter是一个原始类型,会被缓存。除非写成一个函数,否则得不到内部变动后的值。
//lib.js
var counter = 3
function incCounter() {
counter++
}
module.exports = {
get counter(){ //输出的counter属性实际上是个取值器函数。
return counter
},
incCounter: incCounter
}
main.js
var mod = require ('./lib')
console.log(mod.counter)
mod.incCounter()
console.log(mod.counter)//现在再执行就能正确读取内部变量counter的变动了。
3
4
ES6模块的运行机制与CommonJS不一样。JS引擎对脚本静态分析的时候,遇到模块加载命令import就会生成一个只读引用。等到脚本真正执行的时候,再根据这个只读引用到被加载的模块中取值。因此,ES6模块是动态引用,并且不会缓存值,模块里的变量绑定其所在的模块。
// lib.js
export let counter = 3
export function incCounter() {
counter++
}
//main.js
import { counter, incCounter } from './lib'
console.log(counter)
incCounter()
console.log(counter)
3
4
上面的代码说明,ES6模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。
再如:
//m1.js
export var foo = 'bar'
setTimeout(()=>foo='baz',500)
//m2.js
import {foo} from './m1.js'
console.log(foo)
setTimeout(()=>console.log(foo),500)
bar
baz
上面的代码表明,ES6模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
由于ES6输入的模块变量只是一个“符号连接”,所以这个变量是只读的,对它重新赋值会报错。
//lib.js
export let obj = {}
//main.js
import {obj} from './lib'
obj.prop=123 //OK
obj = {} //TypeError
main.js 从 lib.js 输入变量obj,可以对obj添加属性,但是重新赋值就会报错。因为变量obj指向的地址是只读的,不能重新赋值,这就好比main.js创造了一个名为obj的const变量。
//mod.js
function C(){
this.sum = 0
this.add = function(){
this.sum += 1
}
this.show = function(){
console.log(this.sum)
}
}
export let c = new C()
//x.js
import {c} from './mod'
c.add()
//y.js
import {c} from './mod'
c.show()
//main.js
import './x'
import './y'
1
这就证明了x.js和y.js加载都是C的同一实例
摘抄自:
阮一峰-ES6标准入门-第六章-Module的加载实现
from step-by-step.
ES6
- ES6模块中的值属于动态只读引用。
- 对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
- 对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
- 循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。
CommonJS
- 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
- 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
- 当使用require命令加载某个模块时,就会运行整个模块的代码。
- 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
- 当循环加载时,脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
from step-by-step.
commonJS是require()方法 相当于值的拷贝
会有属性缓存 一旦输出 内部的基本类型值的改变不会对输出发生影响 引用类型会
import 是声明时暴漏的接口 只有在使用时才会去根据这个接口取值 没有属性缓存
对复杂数据类型来说 export default值 也会受到改变 而基本数据类型不会
from step-by-step.
ES6模块是引用,重新赋值会编译报错,不能修改其变量的指针指向,但可以改变内部属性的值;
CommonJS模块是拷贝(浅拷贝),可以重新赋值,可以修改指针指向;
from step-by-step.
ES6是只读的 主要阐述CommonJS吧 之前面试遇到过这个题
CommonJS:
对于变量属于复制 存在缓存 此模块的变量可以被其他模块所修改
// b.js
let count = 1
let plusCount = () => {
count = 99
}
setTimeout(() => {
console.log('lala', count)
}, 4000)
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)
mod.count = 3
console.log('a.js-3', mod.count)
node a.js
a.js-1 1
a.js-2 1
a.js-3 3
lala 99 // 四秒后
复杂数据类型属于浅复制
// b.js
let count = 1
let obj = [{val: 1}]
let plusCount = () => {
count = 99
}
let plusObj = () => {
obj[0].val = 999
}
setTimeout(() => {
console.log('lala', obj)
}, 4000)
module.exports = {
count,
plusCount,
plusObj,
obj
}
// a.js
let mod = require('./b.js')
console.log('a.js-1', mod.obj)
mod.plusObj()
console.log('a.js-2', mod.obj)
node a.js
a.js-1 [{val: 99}]
a.js-2 [{val: 99}]
lala [{val: 99}]
from step-by-step.
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJs模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化不会影响到这个值
// common.js
var count = 1;
var printCount = () =>{
return ++count;
}
module.exports = {
printCount: printCount,
count: count
};
// index.js
let v = require('./common');
console.log(v.count); // 1
console.log(v.printCount()); // 2
console.log(v.count); // 1
你可以看到明明common.js里面改变了count,但是输出的结果还是原来的。这是因为count是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动的值。将common.js里面的module.exports 改写成
module.exports = {
printCount: printCount,
get count(){
return count
}
};
这样子的输出结果是 1,2,2
而在ES6当中,写法是这样的,是利用export 和import导入的
// es6.js
export let count = 1;
export function printCount() {
++count;
}
// main1.js
import { count, printCount } from './es6';
console.log(count)
console.log(printCount());
console.log(count)
ES6模块是动态引用,并且不会缓存,模块里面的便令绑定其所在的模块,而是动态地去加载值,并且不能重新复制
另外还想说一个export default
let count = 1;
function printCount() {
++count;
}
export default { count, printCount}
// main3.js
import res form './main3.js'
console.log(res.count)
from step-by-step.
照抄阮大神的书
- CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用
- CommonJS模块是运行时加载,ES6模块是编译时输出接口
第二个差异是因为CommonJS加载的是一个对象,即module.export属性,该对象只有在脚本运行结束时才会生成。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
下面重点解释第一个差异。
CommonJS模块输出的是值的复制,一旦输出这个值,模块内部的变化就影响不到这个值。//lib.js 一个commonJS模块 var counter = 3 function incCounter() { counter++ } module.exports = { counter : counter, incCounter : incCounter, }
//main.js 在这个函数里加载这个模块 var mod = require ('./lib') console.log(mod.counter) mod.incCounter() console.log(mod.counter)
3 3
上面的代码说明,lib.js模块加载后,它的内部变化就影响不到输出的mod.counter 了。
这是因为mod.counter是一个原始类型,会被缓存。除非写成一个函数,否则得不到内部变动后的值。//lib.js var counter = 3 function incCounter() { counter++ } module.exports = { get counter(){ //输出的counter属性实际上是个取值器函数。 return counter }, incCounter: incCounter }
main.js var mod = require ('./lib') console.log(mod.counter) mod.incCounter() console.log(mod.counter)//现在再执行就能正确读取内部变量counter的变动了。
3 4
ES6模块的运行机制与CommonJS不一样。JS引擎对脚本静态分析的时候,遇到模块加载命令import就会生成一个只读引用。等到脚本真正执行的时候,再根据这个只读引用到被加载的模块中取值。因此,ES6模块是动态引用,并且不会缓存值,模块里的变量绑定其所在的模块。
// lib.js export let counter = 3 export function incCounter() { counter++ } //main.js import { counter, incCounter } from './lib' console.log(counter) incCounter() console.log(counter) 3 4
上面的代码说明,ES6模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。
再如://m1.js export var foo = 'bar' setTimeout(()=>foo='baz',500) //m2.js import {foo} from './m1.js' console.log(foo) setTimeout(()=>console.log(foo),500) bar baz
上面的代码表明,ES6模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
由于ES6输入的模块变量只是一个“符号连接”,所以这个变量是只读的,对它重新赋值会报错。//lib.js export let obj = {} //main.js import {obj} from './lib' obj.prop=123 //OK obj = {} //TypeError
main.js 从 lib.js 输入变量obj,可以对obj添加属性,但是重新赋值就会报错。因为变量obj指向的地址是只读的,不能重新赋值,这就好比main.js创造了一个名为obj的const变量。
//mod.js function C(){ this.sum = 0 this.add = function(){ this.sum += 1 } this.show = function(){ console.log(this.sum) } } export let c = new C()
//x.js import {c} from './mod' c.add()
//y.js import {c} from './mod' c.show()
//main.js import './x' import './y' 1
这就证明了x.js和y.js加载都是C的同一实例
摘抄自:
阮一峰-ES6标准入门-第六章-Module的加载实现
还是峰哥牛批
from step-by-step.
Related Issues (20)
- 实现 Promise.race 方法 HOT 13
- JSONP原理及简单实现 HOT 17
- 实现一个数组去重的方法 HOT 24
- 清除浮动的方法有哪些? HOT 17
- 编写一个通用的柯里化函数currying HOT 14
- 原型链继承的优缺点是什么?使用原型链继承实现 Dog 继承 Animal HOT 19
- 借用构造函数和组合继承(伪经典继承)的优缺点是什么? HOT 11
- 原型式继承的基本**是什么?有什么优缺点? HOT 13
- 寄生式继承的基本思路是什么?有什么优缺点? HOT 7
- 寄生组合式继承的基本**是什么?有哪些优缺点? HOT 11
- 实现一个 JSON.stringify HOT 9
- 实现一个 JSON.parse HOT 9
- 实现一个观察者模式 HOT 10
- 使用CSS让一个元素水平垂直居中 HOT 11
- 如何使用Proxy实现简单MVVM HOT 2
- 以下代码的输出的结果为: HOT 7
- 列举常见的JS和CSS兼容性问题
- 介绍下 Set、Map、WeakSet 和 WeakMap 的区别? HOT 1
- Vue组件间是怎么进行参数传递的? HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from step-by-step.