最近測(cè)試了幾個(gè) ES module 和 Commonjs 的例子,理解了之前不太理解的概念,記錄一下。要是想多了解的可以去看看阮老師的 Module 那部分。會(huì)貼一小部分的代碼,不會(huì)貼所有驗(yàn)證的代碼。
Commonjs require 大概流程
本質(zhì)上 Commonjs 一直是 node 在使用的規(guī)范,雖然其他平臺(tái)也可以使用。
- 處理路徑,node 有專門的 path 模塊和__dirname 等,將路徑轉(zhuǎn)成絕對(duì)路徑,定位目標(biāo)文件
- 檢查緩存
- 讀取文件代碼(fs)
- 包裹一個(gè)函數(shù)并執(zhí)行(自執(zhí)行函數(shù))
- 緩存
- 返回 module.exports
ES module 大概流程
最重要的應(yīng)該是解析依賴了,ES module 如果都是同步的,會(huì)很慢。都說(shuō) ES module 是異步的,在不同環(huán)境會(huì)有不同的結(jié)果。其實(shí) ES module 的三個(gè)步驟是可以分開異步進(jìn)行。在瀏覽器,會(huì)使用 HTML 的規(guī)范,最后的實(shí)例化是同步,在 node 環(huán)境,文件都是在本地,同步就顯得很容易。
- 模塊解析,入口文件開始,構(gòu)建 Module Record,然后放置到 Module Map。Module Map 相當(dāng)于每一個(gè) js 文件,Module Record 相當(dāng)于里面依賴的每一個(gè) import
- 獲取文件,解析文件,進(jìn)行 JavaScript 的解析,變量提升等
- 實(shí)例化,執(zhí)行文件內(nèi)容
exports 與 module.exports
Commonjs 可以用 exports.xxx 導(dǎo)出,也可以用 module.exports = {}導(dǎo)出,因?yàn)檎麄€(gè)文件讀取之后會(huì)包裹到一個(gè)自執(zhí)行函數(shù),差不多是這樣:
(function(exports, require, module, filename, dirname){
})(exports, require, module, filename, dirname)
如果直接 exports = {}那么導(dǎo)出是無(wú)效的。下面三個(gè)例子就可以很好的理解:
function fn(obj){
obj.num = 2;
};
let obj = {
num: 1;
};
fn(obj);
console.log(obj);
function fn(obj){
obj = {num: 2};
};
let obj = {
num: 1;
};
fn(obj);
console.log(obj);
function fn(obj){
obj = 2;
};
let obj = 1;
fn(obj);
console.log(obj);
對(duì)象是指針的引用,相當(dāng)于 obj = xxxx,用 obj.xx 賦值其實(shí)就是給指針 xxxx 指向的對(duì)象賦值,如果 obj = {},相當(dāng)于 obj 的指針改變了,相當(dāng)于 obj = xx,所以 exports = {}是無(wú)效的。
ES module 是值的引用,Commonjs 是值的拷貝
這塊其實(shí)挺好實(shí)驗(yàn)的,導(dǎo)出一個(gè)變量,調(diào)用函數(shù)改變這個(gè)變量再輸出,可以得到 Commonjs 的值是不會(huì)因?yàn)閳?zhí)行了 add 就改變,ES module 就會(huì):
let a = 10;
exports.a = a;
exports.add = () => {
a++;
};
let a = 10;
export const b = a;
exports.add = () => {
a++;
};
ES module 是編譯時(shí)輸出,Commonjs 是運(yùn)行時(shí)加載
運(yùn)行時(shí)加載也比較好實(shí)驗(yàn)(個(gè)人觀點(diǎn)這樣可以表示是運(yùn)行時(shí)加載):
main.js
let a = require('./a.js');
let b = require('./b.js');
a.js
exports.a = 'a';
let b = require('./b.js');
exports.aa = 'aa';
b.js
let a = require('./a.js');
console.log(a,'in b.js');
這樣去執(zhí)行的時(shí)候,b.js 里面的 a 是{a: ‘a(chǎn)’},如果把 exports.aa = ‘a(chǎn)a’;放到 let b = require(‘./b.js’);之前,b.js 里面的 a 是{a: ‘a(chǎn)’, aa: ‘a(chǎn)a’}。
所以 Commonjs 是一邊運(yùn)行一邊加載,當(dāng) a.js 執(zhí)行到 let b = require(‘./b.js’);的時(shí)候,之前的代碼是執(zhí)行過(guò)了,并緩存起來(lái),這時(shí)候就會(huì)去加載 b.js 并執(zhí)行。
ES module 是編譯時(shí)輸出
不太確定是否能這樣理解:
index.js
import {c} from './c.js';
c.js
import n5n3t3z from './d.js';
export let c = 'c';
d.js
import {c} from './c.js';
console.log(c,'in d.js');
export const d = 'd';
得到的結(jié)果會(huì)報(bào)錯(cuò):Cannot access ‘c’ before initialization,如果 let c 改成 var c,結(jié)果是 undefined in d.js。
ES module 會(huì)有一個(gè)跟 JavaScript 解析一樣的過(guò)程,先是解析整個(gè) js,做一些變量提升,然后再執(zhí)行。就是說(shuō)會(huì)先加載所有的文件,并且解析,不會(huì)執(zhí)行,在所有依賴文件加載解析完成,再開始執(zhí)行。所以我是這樣去理解的 ES module 是編譯時(shí)輸出。
ES module 和 Commonjs 循環(huán)引用的區(qū)別
這點(diǎn)其實(shí)挺重要的,ES module 和 Commonjs 都是通過(guò)緩存來(lái)解決循環(huán)引用的問(wèn)題,不會(huì)造成死循環(huán)。Commonjs 是運(yùn)行時(shí)加載,在解析到 require 的時(shí)候,會(huì)先檢查緩存,如果沒(méi)有,會(huì)先進(jìn)行緩存再繼續(xù)往下執(zhí)行:
main.js
require('./a.js');
a.js
let b = require('./b.js');
exports.a = 'a';
console.log('a.js', b);
b.js
let a = require('./a.js');
console.log('b.js', a);
exports.b = 'b';
result:
b.js {}
a.js { b: 'b' }
大概流程:
- main.js require(‘./a.js’); 檢查緩存,沒(méi)有 a.js,執(zhí)行 a.js
- a.js,檢查緩存,沒(méi)有,緩存 a.js。執(zhí)行 let b = require(‘./b.js’);,檢查緩存,沒(méi)有 b.js,執(zhí)行 b.js
- b.js,檢查緩存,沒(méi)有 b.js,緩存 b.js。執(zhí)行 let a = require(‘./a.js’);,檢查緩存,有 a.js,獲取緩存,打印獲取的緩存,b.js 緩存加上 b: ‘b’
- 回到 a.js,a.js 緩存加上 a: ‘a(chǎn)’,打印
所以 Commonjs 多次引入和循環(huán)引入的解決方案,是先緩存,再根據(jù)執(zhí)行的內(nèi)容新增緩存的內(nèi)容,而且只會(huì)執(zhí)行一次。
ES module 解決多次引入和循環(huán)引入也是依賴緩存,但是緩存的機(jī)制不一樣。ES module 是值的引用和編譯時(shí)輸出,ES module 導(dǎo)出的是內(nèi)存地址的索引:
index.js
import {c} from './c.js';
c.js
import n5n3t3z from './d.js';
console.log(d,'in c.js');
export var c = 'c';
d.js
import {c} from './c.js';
console.log(c,'in d.js');
export const d = 'd';
result
undefined in d.js
d in c.js
當(dāng)解析到 d.js 的 import {c} from ‘./c.js’;,會(huì)去 module map 檢查是否有 c moduel record,有,建立模塊指向。當(dāng)依賴解析完成之后,代碼也解析完成了,最后實(shí)例化運(yùn)行代碼,所以 d.js 執(zhí)行的時(shí)候 c 是 undefined。
ES module 動(dòng)態(tài)引入 import()
Commonjs 的 require 可以是動(dòng)態(tài)的,也不一定要放在頂層,ES module 的 import 就必須放在最頂層。動(dòng)態(tài)加載在實(shí)際應(yīng)用場(chǎng)景是必須的,對(duì)于性能方面有非常大的提升。最典型的就是路由懶加載,如果不是有動(dòng)態(tài) import,打包出來(lái)的是一個(gè)文件,首次加載會(huì)非常慢。還有是一些條件語(yǔ)句決定是否加載某些文件,對(duì)性能也非常友好。
tree shaking
ES module 可以實(shí)現(xiàn) tree shaking,核心就是 ES module 是編譯時(shí)輸出,新進(jìn)行編譯再執(zhí)行,編譯過(guò)程就能確定哪些內(nèi)容是無(wú)用的,Commonjs 就無(wú)法實(shí)現(xiàn),只有在執(zhí)行過(guò)程中才知道哪些內(nèi)容是無(wú)用的。
node 執(zhí)行 ES module
如果文件后綴是.mjs(node 執(zhí)行的后綴是.cjs),那么 node 會(huì)根據(jù) ES module 規(guī)范去執(zhí)行,如果是 js,那么 package.json 里面要新增"type": “module”,否則會(huì)報(bào)錯(cuò):
Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
SyntaxError: Cannot use import statement outside a module
也可以配置 exports 做兼容,exports 優(yōu)先級(jí)高于 main:
"exports": {
"import": "./src/index.js",
"require": "./src/index.cjs"
}
require 尋找引入的順序
先看是否是內(nèi)置包,如果是直接返回;看是否是相對(duì)路徑,是就處理成可識(shí)別絕對(duì)路徑,如果找不到就報(bào)錯(cuò);不是內(nèi)置包沒(méi)有相對(duì)路徑,從當(dāng)前目錄開始尋找 node_modules,找不到依次往上的目錄尋找 node_modules,直到根目錄,還找不到就報(bào)錯(cuò)。會(huì)先以文件名找,再依次是.js、.json、.node。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-532644.html
歡迎關(guān)注訂閱號(hào) coding個(gè)人筆記文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-532644.html
到了這里,關(guān)于了解一下ES module 和 Commonjs的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!