JS中ESModule和commonjs介紹及使用區別

ES Module

導出

僅導出

  • named exports: 命名導出,每次可以導出一個或者多個。
  • default exports: 默認導出,每次隻能存在一個。

以上兩者可以混合導出:

    // 命名導出
    export const b = 'b'
    // 默認導出
    export default {
      a: 1
    };

    const c = 'c'
    export { c }
    
    // 以上內容會合並導出,即導出為: {b:'b', c:'c', default: {a:1}}

更多示例可以直接去看mdn

重導出(re-exporting / aggregating)

算是一個導入再導出的一個語法糖吧。

  export {
    default as function1,
    function2,
  } from 'bar.js';

  // 等價於
  import { default as function1, function2 } from 'bar.js';
  export { function1, function2 };

然而這種語法是會報錯的:

export DefaultExport from 'bar.js'; // Invalid

正確的語法應該是:

export { default as DefaultExport } from 'bar.js'; // valid

我猜是因為export 本身支持的export xxx這種語法必須是要導出一個對象,然而import xxx可能是任意類型,兩者沖突瞭,所以從編譯層面就不讓這種語法生效會更好。

嵌入式腳本

嵌入式腳本不可以使用export。

引入

語法

  • import all exports: import * as allVar,所有導出內容,包含命名導出及默認導出。allVar會是一個對象,默認導出會作為allVar的key名為default對應的值。
  • import named exports: import {var1, var2},引入命名導出的部分。沒找到,對應的值就為undefined。個人覺得可以看做是"import all exports"的解構語法。
  • import default exports: import defaultVar,引入默認導出的部分。
  • import side effects: import "xxx./js",僅運行這個js,可能是為瞭獲取其副作用。
    // test.js
    export const b = 'b'     // 命名導出
    export default {    // 默認導出
      a: 1
    };

    // index.js
    import { b, default as _defaultModule } from './test.js'
    import defaultModule from './test.js'
    import * as allModule from './test.js'

    console.log('name export', b) // 'b'
    console.log('default export', defaultModule) // {a:1}
    console.log(_defaultModule === defaultModule) // true
    console.log('all export', allModule) // {b:'b', default: {a:1}}

一個之前老記錯的case

    // test.js
    export default {    // 默認導出
      a: 1
    };

    // index.js
    import { a } from './test.js'
    console.log('name export', a) // undefined

    // index.js
    import defaultModule from './test.js'
    import * as allModule from './test.js'
    console.log('default export', defaultModule) // {a:1}
    console.log('all export', allModule) // {default: {a:1}}

嵌入式腳本

嵌入式腳本引入modules時,需要在script上增加 type="module"。

特點

live bindings:

  • 通過export在mdn上的解釋,export導出的是live bindings,再根據其他文章綜合判斷,應該是引用的意思。即export導出的是引用
  • 模塊內的值更新瞭之後,所有使用export導出值的地方都能使用最新值。

read-only:通過import在mdn上的解釋,import使用的是通過export導出的不可修改的引用

strict-mode:被引入的模塊都會以嚴格模式運行。

靜態引入、動態引入

import x from這種語法有syntactic rigid,需要編譯時置於頂部且無法做到動態引入加載。如果需要動態引入,則需要import ()語法。有趣的是,在mdn上,前者分類到瞭 Statements & declarations, 後者分類到瞭 Expressions & operators。這倆是根據什麼分類的呢?

  true && import test from "./a.js";
  // SyntaxError: import can only be used in import() or import.meta
  // 這裡應該是把import當成瞭動態引入而報錯

示例:

  // a.js
  const test = {
    a: 1
  };
  export default test;
  // 改動模塊內部的值
  setTimeout(() => {
    test.a = 2;
  }, 1000);

  // index.js
  import test from './index.js'

  /* live bindings */
  console.log(test) // {a:1}
  setTimeout(()=>{
    console.log(test) // {a:2}
  }, 2000)

  /* read-only */
  test= { a: 3 } // 報錯, Error: "test" is read-only.

  /* syntactically rigid */
  if(true){
    import test from './index.js' // 報錯, SyntaxError: 'import' and 'export' may only appear at the top level
  }

commonJS

導出

在 Node.js 模塊系統中,每個文件都被視為獨立的模塊。模塊導入導出實際是由nodejs的模塊封裝器實現,通過為module.exports分配新的值來實現導出具體內容。

module.exports有個簡寫變量exports,其實就是個引用復制。exports作用域隻限於模塊文件內部。 原理類似於:

// nodejs內部
exports = module.exports

console.log(exports, module.exports) // {}, {}
console.log(exports === module.exports) // true

註意:nodejs實際導出的是module.exports,以下幾種經典case單獨看一下:

case1:

// ✅使用exports
exports.a = xxx
console.log(exports === module.exports) // true

// ✅等價於
module.exports.a = xxx

case2:

// ✅這麼寫可以導出,最終導出的是{a:'1'}
module.exports = {a:'1'}

console.log(exports, module.exports) // {}, {a:'1'}
console.log(exports === module.exports) // false


// ❌不會將{a:'1'}導出,最終導出的是{}
exports = {a:'1'}

console.log(exports, module.exports) // {a:'1'}, {}
console.log(exports === module.exports) // false

引入

通過require語法引入:

// a是test.js裡module.exports導出的部分
const a = require('./test.js')

原理偽代碼:

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // Module code here. In this example, define a function.
    function someFunc() {}
    exports = someFunc;
    // At this point, exports is no longer a shortcut to module.exports, and
    // this module will still export an empty default object.
    module.exports = someFunc;
    // At this point, the module will now export someFunc, instead of the
    // default object.
  })(module, module.exports);
  return module.exports;
}

特點

值拷貝

// test.js
let test = {a:'1'}
setTimeout(()=>{
  test = {a:'2'}
},1000)
module.exports = test

// index.js
const test1 = require('./test.js')
console.log(test1) // {a:1}
setTimeout(()=>{
  console.log(test1) // {a:1}
},2000)

ES Module和 commonJS區別

語法:

exportsmodule.exportsrequire 是Node.js模塊系統關鍵字。

exportexport defaultimport 則是ES6模塊系統的關鍵字:

原理:

exportsmodule.exports導出的模塊為值復制。

exportexport default為引用復制。

時機:

ES Module靜態加載是編譯時確定,ES Module動態加載是運行時確定。

CommonJS是運行時確定。

到此這篇關於JS中ESModule和commonjs介紹及使用區別的文章就介紹到這瞭,更多相關JS ESModule和commonjs 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: