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();
複製程式碼
在終端執行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 // 可以改變屬性值 複製程式碼
在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
時序圖下的執行步驟分解圖如下所示:
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 複製程式碼
時序圖下的執行步驟分解圖如下所示: