YvetteLau / Step-By-Step

不积跬步,无以至千里;
705 stars 66 forks source link

ES6模块和CommonJS模块有哪些差异? #43

Open YvetteLau opened 5 years ago

shenshuangdao commented 5 years ago

ES6模块是引用,重新赋值会编译报错,不能修改其变量的指针指向,但可以改变内部属性的值; CommonJS模块是拷贝(浅拷贝),可以重新赋值,可以修改指针指向;

luohong123 commented 5 years ago

ES6

  1. ES6模块中的值属于动态只读引用。
  2. 对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
  3. 对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
  4. 循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。

    CommonJS

  5. 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
  6. 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
  7. 当使用require命令加载某个模块时,就会运行整个模块的代码。
  8. 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
  9. 当循环加载时,脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
jodiezhang commented 5 years ago

照抄阮大神的书

  1. CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用
  2. 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的加载实现
xinqiymsz commented 5 years ago

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}]
YvetteLau commented 5 years ago
1. CommonJS 模块是运行时加载,ES6模块是编译时输出接口。
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

模块输出的值是复杂数据类型

  1. 模块输出的是对象,属性值是简单数据类型时:
//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 关联,defaulthobbies 中存储的是同一个对象的堆内存地址,当这个对象的值发生改变时,此时 default 的值也会发生变化。

MissNanLan commented 5 years ago

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)
chongyangwang commented 5 years ago

commonJS是require()方法 相当于值的拷贝
会有属性缓存 一旦输出 内部的基本类型值的改变不会对输出发生影响 引用类型会

import 是声明时暴漏的接口 只有在使用时才会去根据这个接口取值 没有属性缓存

对复杂数据类型来说 export default值 也会受到改变 而基本数据类型不会

ihengshuai commented 4 years ago

照抄阮大神的书

  1. CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用
  2. 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的加载实现

还是峰哥牛批