先給 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.exports和exports的關(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 },這樣保持了exports和module.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 里的代碼。
- named import: 與 named export 對(duì)應(yīng)
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 的原理等等等等就要花更多功夫啦。
參考鏈接: