簡(jiǎn)單理解 ES6 module 和 Node.js module

先給 module 在網(wǎng)上找個(gè)自己感覺(jué)妥當(dāng)?shù)亩x,module: 開(kāi)發(fā)者將程序分解成離散功能塊,這些功能塊就稱(chēng)為 module。

在 Node.js 里每個(gè) .js/.json/.node 文件就是一個(gè) module(后面專(zhuān)指 .js 的 module),每個(gè) module 通過(guò) module.exports 來(lái)導(dǎo)出;在其他 module 里通過(guò) require(modulePath) 來(lái)引入別的 module 的 module.exports
這里厘下原先讓自己疑惑的幾個(gè)點(diǎn):

  • module 運(yùn)行的機(jī)制是什么:當(dāng)在一個(gè) module 第一次被 require 時(shí),會(huì)經(jīng)歷 Resolution --> Loading --> Wrapping --> Evaluation --> Caching 這一系列過(guò)程,先通過(guò)傳入 require 的參數(shù)來(lái)確定目標(biāo) module 到底在哪里,是什么;找到 module 后,將 module 文件(比如是個(gè) js 文件)里的內(nèi)容讀到內(nèi)存;下一步是將這些內(nèi)容放到 function (exports, require, module, __filename, __dirname) {...// 引入的 module 的內(nèi)容},并執(zhí)行這個(gè)函數(shù),也就是說(shuō)會(huì)執(zhí)行引入的 module 的代碼,同時(shí)這個(gè) wrapper 函數(shù)為每個(gè) module 提供了 exports, require, module, __filename, __dirname 五個(gè)參數(shù),這也是為什么我們?cè)诿總€(gè) module 可以直接訪(fǎng)問(wèn)這些變量,就好像他們是全局變量一樣;在執(zhí)行完這個(gè) wrapper 函數(shù)之后被 require 的 module 會(huì)被緩存起來(lái),以便在當(dāng)前運(yùn)行周期里再被 require 的時(shí)候不必再歷經(jīng)上面重重步驟,而是直接返回這個(gè) module 的 exports 即可。
  • 在命令行窗口被 node 命令直接的運(yùn)行的 module 會(huì)被所有當(dāng)前運(yùn)行周期里被引用的 module 的 require.main 字段所引用,也就是說(shuō)可以通過(guò) require.main === module 來(lái)判斷某個(gè) module 在被運(yùn)行時(shí)是被 require 的還是直接由 node 的。
  • module.exportsexports 的關(guān)系是什么?在一個(gè) module 里如果不對(duì)這兩個(gè)東西做任何操作的話(huà),console.log 出他們,會(huì)發(fā)現(xiàn)他們的指向的都是一個(gè) {},而且是同一個(gè),也就是說(shuō)默認(rèn)情況下 module.exports === exports 是返回 true 的,所以在給 exports 添加某個(gè)屬性時(shí),比如 exports.a = any 就等于執(zhí)行了 module.exports.a = any,也就等同于將 a 導(dǎo)出,因?yàn)?require 某個(gè) module 的本質(zhì)就是獲取這個(gè) module 的 module.exports;同時(shí)也說(shuō)明 exports = { a: any } 這樣的賦值方式是不會(huì)導(dǎo)出 a 的,因?yàn)?exports 本質(zhì)上只是 module.exports 的一個(gè) copy,上面的賦值只是把 exports 指向了一個(gè)新的對(duì)象,而不會(huì)對(duì) module.exports 有影響,所以會(huì)看到有的庫(kù)里面這樣寫(xiě) module.exports = exports = { a: any },這樣保持了 exportsmodule.exports 的引用始終相同。
  • 循環(huán)引用怎么解決?Node.js 的做法是如果一個(gè) module A require 了 module B,同時(shí) module B 里又 require 了 module A,假設(shè) A 是 main module,那么在執(zhí)行到 require(moduleB) 后再執(zhí)行 module B 里的代碼的時(shí)候,遇到 require(moduleA) 時(shí)會(huì)返回一個(gè) module A 執(zhí)行到 require(moduleB) 這一行時(shí) module.exports 的值的 copy 給 module B,也就是說(shuō),module A 里在 require(moduleB) 之后的導(dǎo)出不會(huì)體現(xiàn)在 module B 里 require(moduleA) 的返回值中。

ES6 的 module,與 Node.js 里的 module 的概念基本相同,都是一個(gè)文件對(duì)應(yīng)一個(gè) module,只不過(guò)這里的文件只包含 .js 的文件。這里只簡(jiǎn)單介紹 import 和 export 的基本用法,其余冗雜或稍微者深層次的東西,下次。

  • export:一個(gè) ES6 module 有兩種形式的 export,一種是 named export,另一種是 default export,兩者可同時(shí)存在于一個(gè) module,在一個(gè) module 中前者只能有一個(gè),后者可以多個(gè)

    • named export:可以在定義的時(shí)候就 export,此時(shí)大致寫(xiě)法為
    export const b = any
    export const a = any
    

    也可以先定義,然后統(tǒng)一 export,此時(shí)大致寫(xiě)法為

    const a = any
    const b = any
    
    export { a, b }
    
    • default export:同上面一樣:export default function() {} 或者
    function A() {}
    export default A
    

    此外,可以在 named export 的統(tǒng)一 export 中指定一個(gè) default export(前提是當(dāng)前 module 沒(méi)有明確指定另外的 default export),比如 export { a as default, b },此時(shí) a 就成了 module 的 default export;其實(shí)這里的 as 的本質(zhì)不過(guò)是重命名,既然可以重命名為 default(當(dāng)把一個(gè) named export 重命名為 default 的時(shí)候,這個(gè) named default 就變成了當(dāng)前 module 的 export default),也可以把一個(gè) named export 的 name 重命名為另一個(gè) name。

  • import:import 與 export 一樣,也有 named import 和 default import,但除此之外多出一個(gè) namespace import 和 empty import

    • named import: 與 named export 對(duì)應(yīng) import { a, b } from 'B',當(dāng)然也有 import { default as a, b } from 'B',即將 module B 中的 default export 重命名為 a。其實(shí) named import 的這個(gè) {} 起作用的機(jī)制和對(duì)象的結(jié)構(gòu)賦值差不多,module B,導(dǎo)出一個(gè)對(duì)象,對(duì)象里的東西都有各自的 key,其中 default export 對(duì)應(yīng)的 key 為 default,其余 named default 的 key 為她們?cè)诒?named export 時(shí)制定的 name。
    • default import:只需要 import a from 'B' 就可以了,這樣取得的 a 就是 module B 的 default export。
    • namespace import:named import 適用于只想 import 目標(biāo) module 里的部分 export,如果想一下子把 module B 里的眾多 export 全都 import,那么挨個(gè)寫(xiě)出那些 export 的 name,未免有點(diǎn)太傻,這時(shí)候就可以用 namespace import。寫(xiě)法為 import * as namespaceName from 'B',這里 'as nameSpaceName' 是必須的,必須為 import 的對(duì)象重新命名,這樣 nameSpaceName 就是一個(gè)包含 module B 所有 export 的對(duì)象啦。
    • empty import:寫(xiě)法為 import 'B',作用是當(dāng) B 為當(dāng)前運(yùn)行周期的第一次被 import 的時(shí)候,執(zhí)行 module B 里的代碼。

import 和 export 的基本用法就是上面這些。除此之外,export 還有些簡(jiǎn)便的寫(xiě)法,稱(chēng)之為 Re-exporting,這適用于一個(gè)文件里將所有 module 的 export 統(tǒng)一到這個(gè)文件夾下的 index.js 的文件中的情形,這有利于確定一個(gè)模塊的邊界,所有的對(duì)外提供的東西只在文件夾下的 index.js 文件中呈現(xiàn)。比如我們現(xiàn)在一個(gè) util 文件夾,里面有 moduleA export { a },moduleB export { b } 和一個(gè) index.js 文件,為了把 a 和 b 對(duì)外呈現(xiàn),那我們?cè)?index.js 中會(huì)這樣寫(xiě)

import { a } from './moduleA'
import { b } from './moduleB'

export { a ,b }

那有了 Re-exporting 之后,可以寫(xiě)成

export { a } from './moduleA'
export { b } from './moduleB'

這樣會(huì)少些一行,但我想好處應(yīng)該不只是少些一行這么簡(jiǎn)單吧,我記得在哪里看到兩者好像是有點(diǎn)區(qū)別的。當(dāng)然不止是 named export/import 可以這樣, default import/export 也可以這樣,這樣除了上面的 named import -> named export 的組合之外還有三種組合

  • default import -> default export:export a from 'moduleA'
  • default import -> named export:export { default as a } from 'moduleA'
  • named import -> default export:export { a as default } 'moduleA'

關(guān)于 ES6 import/export 的基本用法就這些了,掌握了應(yīng)該就差不多能應(yīng)付日常的需要,但是如果想知道一些「為什么」以及「有什么用」的知識(shí),這點(diǎn)肯定不夠的,比如 ES6 module 和 Node.js module 的異同、各自產(chǎn)生的歷史原因也就是 JavaScript 模塊化的演進(jìn)、了解關(guān)于 dynamic import 的提案、webpack 的 Tree Shaking 和 dynamic load 的原理等等等等就要花更多功夫啦。

參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容