1. 程式人生 > >ES6與CommonJS中的模組處理的區別

ES6與CommonJS中的模組處理的區別

ES6和CommonJS都有自己的一套處理模組化程式碼的措施,即JS檔案之間的相互引用。

為了方便兩種方式的測試,使用nodejs的環境進行測試

CommonJS的模組處理

使用require來引入其他模組的程式碼,使用module.exports來引出

// exportDemo.js
count = 1;
module.exports.count = count;
module.exports.Hello = function() {
 var name;
 this.setName = function(newName) {
 name = newName;
 }
 this.sayHello = function() {
 console.log("hello Mr." + name);
 }
 this.getId = function() {
 return count++
 }
}
// requireDemo.js
var {Hello} = require("./exportDemo")
var hello = new Hello();
 
hello.setName("Blank");
hello.sayHello();

前端全棧學習交流圈:866109386,面向1-3經驗年前端開發人員,幫助突破技術瓶頸,提升思維能力,群內有大量PDF可供自取,更有乾貨實戰專案視訊進群免費領取。

在終端執行 node requireDemo.js,返回結果為’hello Mr.Blank’

匯出的Hello函式是原函式的一次拷貝,修改Hello函式的屬性值不會對其他require的地方造成影響

var { Hello, count } = require('./exportDemo')
var hello = new Hello();
// 讓count自增
console.log(hello.getId());
console.log(hello.getId());
// 發現獲取的count還是原值
console.log(count)
 
// 真正的count其實是已經改了的
var newHello = new Hello();
console.log(newHello.getId())
 
var { Hello: newHello, count: newCount } = require('./exportDemo')
console.log(newCount, 'newCount');
// 再次require,取得的newHello和之前require的Hello指向同一個拷貝
console.log(newHello === Hello)

ES6的模組處理

nodejs中執行ES6風格的程式碼

nodejs預設是不支援ES6的模組處理方案的。

但是在8.5.0之後,ES6程式碼的檔案格式定為mjs後,可使用 node --experimental-modules xxx.mjs 執行。

// exportDemo.mjs
export let a = 1;
// importDemo.mjs
import {a} from './exportDemo.mjs'
console.log(a)

與CommonJS模組處理的區別

CommonJS 模組輸出的是一個值的拷貝(已在上一章驗證),ES6 模組輸出的是值的引用。

// exportDemo.mjs
export let counter = 1;
export function incCounter() {
 counter ++;
}
// importDemo.mjs
import { counter, incCounter } from './exportDemo.mjs'
 
incCounter();
console.log(counter)        // 列印結果為2,而不是初始值的1

CommonJS模組是執行時載入,ES6模組是編譯時輸出介面

Nodejs此類的執行環境會在一個閉包中執行CommonJS模組程式碼

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
});

ES6 模組不會快取執行結果,而是動態地去被載入的模組取值,並且變數總是繫結其所在的模組。

// exportDemo.mjs
export let a = 1;
export const b = 2;
export let obj = {};
 
// importDemo.mjs
import { a, b } from './exportDemo.mjs'
console.log(a, b)
a = 1 // 報錯,TypeError: Assignment to constant variable,export出來的值是一個只讀引用
obj.x = 1   // 可以改變屬性值

前端全棧學習交流圈:866109386,面向1-3經驗年前端開發人員,幫助突破技術瓶頸,提升思維能力,群內有大量PDF可供自取,更有乾貨實戰專案視訊進群免費領取。

在ES6模組中我們更多地要去考慮語法的問題 export default

有時候我們會在程式碼發現 export default obj的用法,那麼這個default是用來幹嘛的?

default是ES6引入的與export配套使用的關鍵字,用來給匿名物件、匿名函式設定預設的名字用的

export出來的值必須要有一個命名,否則從語法層次便會報錯

讓我們看一下以下幾個會報錯的錯誤例子

export匿名物件

export { x: 1 } // 報錯,SyntaxError:Unexpected token,這是一個編譯階段的錯誤
 
// 正確寫法
export default { x: 1 }

export匿名函式

export function() {}    // 報錯,SyntaxError: Unexpected token (
 
// 正確寫法
export default function() {}

迴圈引用(recycling loading)

在複雜的模組中,可能會出現模組間的 互相引用 。

commonJS的迴圈引用執行機制

// a.js
exports.loaded = false;
var b = require('./b.js')
console.log("b in a is " + JSON.stringify(b))
exports.loaded = true;
console.log("a complete")
// b.js
exports.loaded = false;
var a = require('./a.js')
console.log("a in b is " + JSON.stringify(a))
exports.loaded = true;
console.log("b complete")
// main.js
var a = require('./a.js')
var b = require('./b.js')
 
console.log("a in main is" + JSON.stringify(a))
console.log("b in main is" + JSON.stringify(b))

執行指令 nodejs main.js

時序圖下的執行步驟分解圖如下所示:

ES6的迴圈引用執行機制

一個會報錯的例子

// a.mjs
import { bar } from './b.mjs'
 
console.log(bar);
export let foo = 'foo from a.mjs'
// b.mjs
import { foo } from './a.mjs'
 
console.log(foo)
 
export let bar = 'bar from b.mjs'
// main.mjs
import { foo } from './a.mjs'
import { bar } from './b.mjs'

node main.mjs

時序圖下的執行步驟分解圖如下所示:

前端全棧學習交流圈:866109386,面向1-3經驗年前端開發人員,幫助突破技術瓶頸,提升思維能力,群內有大量PDF可供自取,更有乾貨實戰專案視訊進群免費領取。

ES6的迴圈引用要特別注意變數是否已被宣告,若未被宣告的塊級作用域變數被其他模組引用時,會報錯。

改進方案:迴圈引用中儘量去export可以提前確定的值(例如函式),其實我們總是希望去** 引用模組執行完全後最終確定的變數 。**

// a.mjs
import { bar } from './b.mjs'
 
console.log(bar());
export function foo() {
 return 'foo from a.mjs'
}
// b.mjs
import { foo } from './a.mjs'
console.log(foo());
export function bar() {
 return 'bar from b.mjs'
}
// main.mjs
import { foo } from './a.mjs'
import { bar } from './b.mjs'

node main.mjs

返回結果:

foo from a.mjs
bar from b.mjs

時序圖下的執行步驟分解圖如下所示: