commonjs
commonjs 中有幾個特別的變量,module、exports、require、global、__filename、__dirname。
在一個 js 文件中輸入下面這行,你會發現可以打印出 5 個 argument。
console.log(arguments);
它們分別對應的就是:exports、require、module、__filename、__dirname。
commonjs 的導出機制比較簡單,只有 module.exports 和 exports。需要注意的是,他們指向的都是同一個對象。如果對 module.exports 賦值,則通過 exports.xxx 導出的所有變量都會失效,它所指向的對象和 module.exports 指向的不是同一個對象,而導出時是以 module.exports 指向的對象為準。
require 是動態導入模塊的,只有執行到 require 語句的時候才會導入模塊,它導入的是模塊的一個拷貝。模塊導入一次之後就會被緩存起來,後面再導入時都是使用的已緩存的版本。
舉個簡單例子,下面代碼會先打印 index 再打印 a。如果把 require 換成 import,則會先打印 a 再打印 index。
// a.js
console.log('a')
// index.js
console.log('index')
require('./a')
node index.js
ESM
常見導入導出
ESM 全稱叫 ECMA Script Modules,是在 ES6 語言層面提出的一種模塊化標準。
ES6 中主要有 import、export 兩個關鍵詞。要注意他們是語法層面的關鍵詞,所以不能使用 console.log(import) 這種方式來打印。而 commonjs 中的變量都是可以打印的。
常見的導入導出:
// 導入
import React from 'react'
import * as React from 'react'
import { Component } from 'react'
import {default as a} from 'react'
import {Button as Btn} from 'antd'
import 'index.less'
export default 1
export const name = 'lxfriday'
export { age }
export {name as nickname}
export { foo } from './foo-bar'
export * from './foo-bar'
ES6 模塊在編譯階段就可以分析出導出的結果,同時它導出的是值的引用。
- 編譯階段會導出意味著模塊的導入會先於正常的執行流執行,即使 import 導入語句是在正常語句之後(見 commonjs 的例子);
- ES6 導出的是一個引用,所以在對原模塊做更改之後會影響新導入的值,導入時可以看做是從 getter 函數里面取值,獲取的都是原模塊內部的值;
下面是一個例子:
image
ESM 在瀏覽器中的應用
ESM 模塊語法可以在比較新的瀏覽器中使用,它的使用方式像下面的代碼,需要使用 type = module 區分。瀏覽器對>
瀏覽器對 module 標籤的腳本是異步加載並執行的,模塊的加載不會阻塞瀏覽器渲染,等到整個頁面渲染完成之後,module 才會執行,這種加載執行策略和 defer 屬性相同(async 是異步加載完就會立即執行,會中斷瀏覽器渲染)。
下面是一個例子
// cc.js
export default { aa: "aaa", bb: "bbb" };
var value = 100;
// index.js
import cc from "./cc.js";
console.log("module in browser", cc);
console.log(value);
hello
執行結果
image
可以看到,模塊正常導入並打印了,但是在 cc.js 中定義的變量 value,在 index.js 中獲取不到,這說明 type=module 有作用域限制。
commonjs 和 ESM 混合使用
當它們混合使用的時候很容易產生混亂,在導入的時候,推薦把 import 語句都放在 require 語句前面。在不同時期,由於語法標準不完善,導入導出都存在差異,下面例子基於 webpack 4.41.2 測試得出。
看看下面的例子:
// 對 import xx from './aa.js'
module.exports = xx
// 等同於
export default xx
// 對 import { aa, bb } from './cc.js' 或者 const { aa, bb } = require('./cc.js')
module.exports = { aa: 'aaa', bb: 'bbb' }
// 等同於
exports.aa = 'aaa'
exports.bb = 'bbb'
// 等同於
export const aa = 'aaa'
export const bb = 'bbb'
下面兩個例子要多加註意
// cc.js
module.exports = { aa: 'aaa', bb: 'bbb' }
// index.js
import cc from './cc.js'
import * as dd from './cc.js'
console.log(cc)
console.log(dd)
// 打印的結果相同,為 {aa: "aaa", bb: "bbb"}
// cc.js
export default { aa: 'aaa', bb: 'bbb' }
// index.js
import cc from './cc.js'
import * as dd from './cc.js'
console.log(cc)
console.log(dd)
// 打印的結果不同
// cc => {aa: "aaa", bb: "bbb"}
// dd => Module {default: {…}, __esModule: true, Symbol(Symbol.toStringTag): "Module"}
可以看出,使用 export default 這種 ES6 方式導出的結果,import * as 可以準確的實現自身的語義,把模塊裡面所有導出的都掛載模塊對象中。
ES6 模塊中,import a from './xx' 是 import { default as a } from './xx' 的簡寫。表示把 xx 模塊中用 export default 導出的變量導入。
參考
- 阮一峰 module-loader
閱讀更多 雲影sky 的文章
關鍵字: 瀏覽器 JavaScript 模塊