CommonJS模塊和ES6模塊的區別

CommonJS模塊與ES6模塊的區別

到目前爲止,已經實習了3個月的時間了。最近在面試,在面試題裏面有題目涉及到模塊循環加載的知識。趁着這個機會,將CommonJS模塊與ES6模塊之間一些重要的的區別作個總結。語法上有什麼區別就不具體說了,主要談談引用的區別。html

轉載請註明出處:CommonJS模塊與es6模塊的區別node

CommonJS

  1. 對於基本數據類型,屬於複製。即會被模塊緩存。同時,在另外一個模塊能夠對該模塊輸出的變量從新賦值。
  2. 對於複雜數據類型,屬於淺拷貝。因爲兩個模塊引用的對象指向同一個內存空間,所以對該模塊的值作修改時會影響另外一個模塊。
  3. 當使用require命令加載某個模塊時,就會運行整個模塊的代碼。
  4. 當使用require命令加載同一個模塊時,不會再執行該模塊,而是取到緩存之中的值。也就是說,CommonJS模塊不管加載多少次,都只會在第一次加載時運行一次,之後再加載,就返回第一次運行的結果,除非手動清除系統緩存。
  5. 循環加載時,屬於加載時執行。即腳本代碼在require的時候,就會所有執行。一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。

ES6模塊

  1. ES6模塊中的值屬於【動態只讀引用】。
  2. 對於只讀來講,即不容許修改引入變量的值,import的變量是隻讀的,不管是基本數據類型仍是複雜數據類型。當模塊遇到import命令時,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。
  3. 對於動態來講,原始值發生變化,import加載的值也會發生變化。不管是基本數據類型仍是複雜數據類型。
  4. 循環加載時,ES6模塊是動態引用。只要兩個模塊之間存在某個引用,代碼就可以執行。

上面說了一些重要區別。如今舉一些例子來講明每一點吧es6

CommonJS

  1. 對於基本數據類型,屬於複製。即會被模塊緩存。同時,在另外一個模塊能夠對該模塊輸出的變量從新賦值。
// 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
  1. 對於複雜數據類型,屬於淺拷貝。因爲兩個模塊引用的對象指向同一個內存空間,所以對該模塊的值作修改時會影響另外一個模塊。
// 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命令加載某個模塊時,就會運行整個模塊的代碼。babel

4.當使用require命令加載同一個模塊時,不會再執行該模塊,而是取到緩存之中的值。也就是說,CommonJS模塊不管加載多少次,都只會在第一次加載時運行一次,之後再加載,就返回第一次運行的結果,除非手動清除系統緩存。dom

5.循環加載時,屬於加載時執行。即腳本代碼在require的時候,就會所有執行。一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。ui

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

仔細說明一下整個過程。code

  1. 在Node.js中執行c模塊。此時遇到require關鍵字,執行a.js中全部代碼。
  2. 在a模塊中exports以後,經過require引入了b模塊,執行b模塊的代碼。
  3. 在b模塊中exports以後,又require引入了a模塊,此時執行a模塊的代碼。
  4. a模塊只執行exports.done = false這條語句。
  5. 回到b模塊,打印b.js-1, exports, b.js-2。b模塊執行完畢。
  6. 回到a模塊,接着打印a.js-1, exports, b.js-2。a模塊執行完畢
  7. 回到c模塊,接着執行require,須要引入b模塊。因爲在a模塊中已經引入過了,因此直接就能夠輸出值了。
  8. 結束。

從以上結果和分析過程能夠看出,當遇到require命令時,會執行對應的模塊代碼。當循環引用時,有可能只輸出某模塊代碼的一部分。當引用同一個模塊時,不會再次加載,而是獲取緩存。htm

ES6模塊

  1. es6模塊中的值屬於【動態只讀引用】。只說明一下複雜數據類型。
  2. 對於只讀來講,即不容許修改引入變量的值,import的變量是隻讀的,不管是基本數據類型仍是複雜數據類型。當模塊遇到import命令時,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。
  3. 對於動態來講,原始值發生變化,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
  1. 循環加載時,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和CommonJSmodule有不瞭解的同窗能夠參考一下如下的文章哈

ES6 module

module的語法

module的加載實現

相關文章
相關標籤/搜索