比較 commonjs 和 ESM 模塊使用方式

commonjs

commonjs 中有幾個特別的變量,moduleexportsrequireglobal__filename__dirnamejavascript

在一個 js 文件中輸入下面這行,你會發現能夠打印出 5 個 argument。html

console.log(arguments);
複製代碼

它們分別對應的就是:exportsrequiremodule__filename__dirnamejava

commonjs 的導出機制比較簡單,只有 module.exportsexports。須要注意的是,他們指向的都是同一個對象。若是對 module.exports 賦值,則經過 exports.xxx 導出的全部變量都會失效,它所指向的對象和 module.exports 指向的不是同一個對象,而導出時是以 module.exports 指向的對象爲準。node

require 是動態導入模塊的,只有執行到 require 語句的時候纔會導入模塊,它導入的是模塊的一個拷貝。模塊導入一次以後就會被緩存起來,後面再導入時都是使用的已緩存的版本。react

舉個簡單例子,下面代碼會先打印 index 再打印 a。若是把 require 換成 import,則會先打印 a 再打印 indexwebpack

// a.js
console.log('a')

// index.js
console.log('index')
require('./a')
複製代碼
node index.js
複製代碼

ESM

常見導入導出

ESM 全稱叫 ECMA Script Modules,是在 ES6 語言層面提出的一種模塊化標準。es6

ES6 中主要有 importexport 兩個關鍵詞。要注意他們是語法層面的關鍵詞,因此不能使用 console.log(import) 這種方式來打印。而 commonjs 中的變量都是能夠打印的。web

常見的導入導出:瀏覽器

// 導入
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 區分。瀏覽器對 script 標籤默認的 type 是 application/javascript

瀏覽器對 module 標籤的腳本是異步加載並執行的,模塊的加載不會阻塞瀏覽器渲染,等到整個頁面渲染完成以後,module 纔會執行,這種加載執行策略和 defer 屬性相同(async 是異步加載完就會當即執行,會中斷瀏覽器渲染)。

<script type="module" src="./index.js">xxx</script>
複製代碼

下面是一個例子

// 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);
複製代碼
<body>
  hello
  <script src="./index.js" type="module"></script>
</body>
複製代碼

執行結果

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 導出的變量導入。

參考

今天的文章就到這裏,感謝閱讀~

歡迎你們關注個人掘金和公衆號

公衆號
相關文章
相關標籤/搜索