“睡服”面试官系列第十一篇之module加载实现(建议收藏学习)
目錄
1. 瀏覽器加載
1.1傳統(tǒng)方法
1.2加載規(guī)則
2. ES6 模塊與 CommonJS 模塊的差異
3. Node 加載
3.1概述
3.2內(nèi)部變量
4ES6 模塊加載 CommonJS 模塊
5CommonJS 模塊加載 ES6 模塊
6循環(huán)加載
6.1CommonJS 模塊的加載原理
6.2CommonJS 模塊的循環(huán)加載
7ES6 模塊的循環(huán)加載
8ES6 模塊的轉(zhuǎn)碼
8.1ES6 module transpiler
8.2SystemJS
總結(jié)
“睡服“面試官系列之各系列目錄匯總(建議學(xué)習(xí)收藏)
1. 瀏覽器加載
1.1傳統(tǒng)方法
HTML 網(wǎng)頁(yè)中,瀏覽器通過 <script> 標(biāo)簽加載 JavaScript 腳本
<!-- 頁(yè)面內(nèi)嵌的腳本 --> <script type="application/javascript"> // module code </script> <!-- 外部腳本 --> <script type="application/javascript" src="path/to/myModule.js"> </script>上面代碼中,由于瀏覽器腳本的默認(rèn)語(yǔ)言是 JavaScript,因此 type="application/javascript" 可以省略。
默認(rèn)情況下,瀏覽器是同步加載 JavaScript 腳本,即渲染引擎遇到 <script> 標(biāo)簽就會(huì)停下來,等到執(zhí)行完腳本,再繼續(xù)向下渲染。如果是外部腳本,還
必須加入腳本下載的時(shí)間。
如果腳本體積很大,下載和執(zhí)行的時(shí)間就會(huì)很長(zhǎng),因此造成瀏覽器堵塞,用戶會(huì)感覺到瀏覽器“卡死”了,沒有任何響應(yīng)。這顯然是很不好的體驗(yàn),所以瀏覽
器允許腳本異步加載,下面就是兩種異步加載的語(yǔ)法
上面代碼中, <script> 標(biāo)簽打開 defer 或 async 屬性,腳本就會(huì)異步加載。渲染引擎遇到這一行命令,就會(huì)開始下載外部腳本,但不會(huì)等它下載和執(zhí)行,
而是直接執(zhí)行后面的命令。
defer 與 async 的區(qū)別是: defer 要等到整個(gè)頁(yè)面在內(nèi)存中正常渲染結(jié)束(DOM 結(jié)構(gòu)完全生成,以及其他腳本執(zhí)行完成),才會(huì)執(zhí)行; async 一旦下載
完,渲染引擎就會(huì)中斷渲染,執(zhí)行這個(gè)腳本以后,再繼續(xù)渲染。一句話, defer 是“渲染完再執(zhí)行”, async 是“下載完就執(zhí)行”。另外,如果有多個(gè) defer
腳本,會(huì)按照它們?cè)陧?yè)面出現(xiàn)的順序加載,而多個(gè) async 腳本是不能保證加載順序的。
1.2加載規(guī)則
瀏覽器加載 ES6 模塊,也使用 <script> 標(biāo)簽,但是要加入 type="module" 屬性。
<script type="module" src="./foo.js"></script>上面代碼在網(wǎng)頁(yè)中插入一個(gè)模塊 foo.js ,由于 type 屬性設(shè)為 module ,所以瀏覽器知道這是一個(gè) ES6 模塊。
瀏覽器對(duì)于帶有 type="module" 的 <script> ,都是異步加載,不會(huì)造成堵塞瀏覽器,即等到整個(gè)頁(yè)面渲染完,再執(zhí)行模塊腳本,等同于打開了 <script>
標(biāo)簽的 defer 屬性
如果網(wǎng)頁(yè)有多個(gè) <script type="module"> ,它們會(huì)按照在頁(yè)面出現(xiàn)的順序依次執(zhí)行。
<script> 標(biāo)簽的 async 屬性也可以打開,這時(shí)只要加載完成,渲染引擎就會(huì)中斷渲染立即執(zhí)行。執(zhí)行完成后,再恢復(fù)渲染
一旦使用了 async 屬性, <script type="module"> 就不會(huì)按照在頁(yè)面出現(xiàn)的順序執(zhí)行,而是只要該模塊加載完成,就執(zhí)行該模塊。
ES6 模塊也允許內(nèi)嵌在網(wǎng)頁(yè)中,語(yǔ)法行為與加載外部腳本完全一致
對(duì)于外部的模塊腳本(上例是 foo.js ),有幾點(diǎn)需要注意。
代碼是在模塊作用域之中運(yùn)行,而不是在全局作用域運(yùn)行。模塊內(nèi)部的頂層變量,外部不可見。
模塊腳本自動(dòng)采用嚴(yán)格模式,不管有沒有聲明 use strict 。
模塊之中,可以使用 import 命令加載其他模塊( .js 后綴不可省略,需要提供絕對(duì) URL 或相對(duì) URL),也可以使用 export 命令輸出對(duì)外接口。
模塊之中,頂層的 this 關(guān)鍵字返回 undefined ,而不是指向 window 。也就是說,在模塊頂層使用 this 關(guān)鍵字,是無(wú)意義的。
同一個(gè)模塊如果加載多次,將只執(zhí)行一次。
下面是一個(gè)示例模塊
利用頂層的 this 等于 undefined 這個(gè)語(yǔ)法點(diǎn),可以偵測(cè)當(dāng)前代碼是否在 ES6 模塊之中
const isNotModuleScript = this !== undefined;2. ES6 模塊與 CommonJS 模塊的差異
討論 Node 加載 ES6 模塊之前,必須了解 ES6 模塊與 CommonJS 模塊完全不同。
它們有兩個(gè)重大差異。
CommonJS 模塊輸出的是一個(gè)值的拷貝,ES6 模塊輸出的是值的引用。
CommonJS 模塊是運(yùn)行時(shí)加載,ES6 模塊是編譯時(shí)輸出接口。
第二個(gè)差異是因?yàn)?CommonJS 加載的是一個(gè)對(duì)象(即 module.exports 屬性),該對(duì)象只有在腳本運(yùn)行完才會(huì)生成。而 ES6 模塊不是對(duì)象,它的對(duì)外接
口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會(huì)生成。
下面重點(diǎn)解釋第一個(gè)差異。
CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個(gè)值,模塊內(nèi)部的變化就影響不到這個(gè)值。請(qǐng)看下面這個(gè)模塊文件 lib.js 的例子。
上面代碼輸出內(nèi)部變量 counter 和改寫這個(gè)變量的內(nèi)部方法 incCounter 。然后,在 main.js 里面加載這個(gè)模塊。
// main.js var mod = require('./lib'); console.log(mod.counter); // 3 mod.incCounter(); console.log(mod.counter); // 3上面代碼說明, lib.js 模塊加載以后,它的內(nèi)部變化就影響不到輸出的 mod.counter 了。這是因?yàn)?mod.counter 是一個(gè)原始類型的值,會(huì)被緩存。除非寫
成一個(gè)函數(shù),才能得到內(nèi)部變動(dòng)后的值。
上面代碼中,輸出的 counter 屬性實(shí)際上是一個(gè)取值器函數(shù)?,F(xiàn)在再執(zhí)行 main.js ,就可以正確讀取內(nèi)部變量 counter 的變動(dòng)了。
$ node main.js 3 4ES6 模塊的運(yùn)行機(jī)制與 CommonJS 不一樣。JS 引擎對(duì)腳本靜態(tài)分析的時(shí)候,遇到模塊加載命令 import ,就會(huì)生成一個(gè)只讀引用。等到腳本真正執(zhí)行
時(shí),再根據(jù)這個(gè)只讀引用,到被加載的那個(gè)模塊里面去取值。換句話說,ES6 的 import 有點(diǎn)像 Unix 系統(tǒng)的“符號(hào)連接”,原始值變了, import 加載的值
也會(huì)跟著變。因此,ES6 模塊是動(dòng)態(tài)引用,并且不會(huì)緩存值,模塊里面的變量綁定其所在的模塊。
還是舉上面的例子。
上面代碼說明,ES6 模塊輸入的變量 counter 是活的,完全反應(yīng)其所在模塊 lib.js 內(nèi)部的變化。
再舉一個(gè)出現(xiàn)在 export 一節(jié)中的例子
上面代碼中, m1.js 的變量 foo ,在剛加載時(shí)等于 bar ,過了 500 毫秒,又變?yōu)榈扔?baz 。
讓我們看看, m2.js 能否正確讀取這個(gè)變化。
上面代碼表明,ES6 模塊不會(huì)緩存運(yùn)行結(jié)果,而是動(dòng)態(tài)地去被加載的模塊取值,并且變量總是綁定其所在的模塊。
由于 ES6 輸入的模塊變量,只是一個(gè)“符號(hào)連接”,所以這個(gè)變量是只讀的,對(duì)它進(jìn)行重新賦值會(huì)報(bào)錯(cuò)。
上面代碼中, main.js 從 lib.js 輸入變量 obj ,可以對(duì) obj 添加屬性,但是重新賦值就會(huì)報(bào)錯(cuò)。因?yàn)樽兞?obj 指向的地址是只讀的,不能重新賦值,這就
好比 main.js 創(chuàng)造了一個(gè)名為 obj 的 const 變量。
最后, export 通過接口,輸出的是同一個(gè)值。不同的腳本加載這個(gè)接口,得到的都是同樣的實(shí)例
上面的腳本 mod.js ,輸出的是一個(gè) C 的實(shí)例。不同的腳本加載這個(gè)模塊,得到的都是同一個(gè)實(shí)例
// x.js import {c} from './mod'; c.add(); // y.js import {c} from './mod'; c.show() // main.js import './x'; import './y';現(xiàn)在執(zhí)行 main.js ,輸出的是 1?
$ babel-node main.js 1這就證明了 x.js 和 y.js 加載的都是 C 的同一個(gè)實(shí)例
3. Node 加載
3.1概述
Node 對(duì) ES6 模塊的處理比較麻煩,因?yàn)樗凶约旱?CommonJS 模塊格式,與 ES6 模塊格式是不兼容的。目前的解決方案是,將兩者分開,ES6 模塊
和 CommonJS 采用各自的加載方案。
Node 要求 ES6 模塊采用 .mjs 后綴文件名。也就是說,只要腳本文件里面使用 import 或者 export 命令,那么就必須采用 .mjs 后綴名。 require 命令不
能加載 .mjs 文件,會(huì)報(bào)錯(cuò),只有 import 命令才可以加載 .mjs 文件。反過來, .mjs 文件里面也不能使用 require 命令,必須使用 import 。
目前,這項(xiàng)功能還在試驗(yàn)階段。安裝 Node v8.5.0 或以上版本,要用 --experimental-modules 參數(shù)才能打開該功能
為了與瀏覽器的 import 加載規(guī)則相同,Node 的 .mjs 文件支持 URL 路徑
import './foo?query=1'; // 加載 ./foo 傳入?yún)?shù) ?query=1上面代碼中,腳本路徑帶有參數(shù) ?query=1 ,Node 會(huì)按 URL 規(guī)則解讀。同一個(gè)腳本只要參數(shù)不同,就會(huì)被加載多次,并且保存成不同的緩存。由于這個(gè)
原因,只要文件名中含有 : 、 % 、 # 、 ? 等特殊字符,最好對(duì)這些字符進(jìn)行轉(zhuǎn)義。
目前,Node 的 import 命令只支持加載本地模塊( file: 協(xié)議),不支持加載遠(yuǎn)程模塊。
如果模塊名不含路徑,那么 import 命令會(huì)去 node_modules 目錄尋找這個(gè)模塊。
如果模塊名包含路徑,那么 import 命令會(huì)按照路徑去尋找這個(gè)名字的腳本文件
import 'file:///etc/config/app.json'; import './foo'; import './foo?search'; import '../bar'; import '/baz';如果腳本文件省略了后綴名,比如 import './foo' ,Node 會(huì)依次嘗試四個(gè)后綴名: ./foo.mjs 、 ./foo.js 、 ./foo.json 、 ./foo.node 。如果這些腳
本文件都不存在,Node 就會(huì)去加載 ./foo/package.json 的 main 字段指定的腳本。如果 ./foo/package.json 不存在或者沒有 main 字段,那么就會(huì)依次
加載 ./foo/index.mjs 、 ./foo/index.js 、 ./foo/index.json 、 ./foo/index.node 。如果以上四個(gè)文件還是都不存在,就會(huì)拋出錯(cuò)誤。
最后,Node 的 import 命令是異步加載,這一點(diǎn)與瀏覽器的處理方法相同
3.2內(nèi)部變量
ES6 模塊應(yīng)該是通用的,同一個(gè)模塊不用修改,就可以用在瀏覽器環(huán)境和服務(wù)器環(huán)境。為了達(dá)到這個(gè)目標(biāo),Node 規(guī)定 ES6 模塊之中不能使用
CommonJS 模塊的特有的一些內(nèi)部變量。
首先,就是 this 關(guān)鍵字。ES6 模塊之中,頂層的 this 指向 undefined ;CommonJS 模塊的頂層 this 指向當(dāng)前模塊,這是兩者的一個(gè)重大差異。
其次,以下這些頂層變量在 ES6 模塊之中都是不存在的
arguments
require
module
exports
__filename
__dirname
如果你一定要使用這些變量,有一個(gè)變通方法,就是寫一個(gè) CommonJS 模塊輸出這些變量,然后再用 ES6 模塊加載這個(gè) CommonJS 模塊。但是這樣
一來,該 ES6 模塊就不能直接用于瀏覽器環(huán)境了,所以不推薦這樣做。
上面代碼中, expose.js 是一個(gè) CommonJS 模塊,輸出變量 __dirname ,該變量在 ES6 模塊之中不存在。ES6 模塊加載 expose.js ,就可以得到
__dirname 。
4ES6 模塊加載 CommonJS 模塊
CommonJS 模塊的輸出都定義在 module.exports 這個(gè)屬性上面。Node 的 import 命令加載 CommonJS 模塊,Node 會(huì)自動(dòng)將 module.exports 屬
性,當(dāng)作模塊的默認(rèn)輸出,即等同于 export default xxx 。
下面是一個(gè) CommonJS 模塊。
import 命令加載上面的模塊, module.exports 會(huì)被視為默認(rèn)輸出,即 import 命令實(shí)際上輸入的是這樣一個(gè)對(duì)象 { default: module.exports } 。
所以,一共有三種寫法,可以拿到 CommonJS 模塊的 module.exports 。
上面代碼的第三種寫法,可以通過 baz.default 拿到 module.exports 。 foo 屬性和 bar 屬性就是可以通過這種方法拿到了 module.exports 。
下面是一些例子
上面代碼中, es.js 采用第二種寫法時(shí),要通過 bar.default 這樣的寫法,才能拿到 module.exports?
// c.js module.exports = function two() { return 2; }; // es.js import foo from './c'; foo(); // 2 import * as bar from './c'; bar.default(); // 2 bar(); // throws, bar is not a function上面代碼中, bar 本身是一個(gè)對(duì)象,不能當(dāng)作函數(shù)調(diào)用,只能通過 bar.default 調(diào)用。
CommonJS 模塊的輸出緩存機(jī)制,在 ES6 加載方式下依然有效
上面代碼中,對(duì)于加載 foo.js 的腳本, module.exports 將一直是 123 ,而不會(huì)變成 null 。
由于 ES6 模塊是編譯時(shí)確定輸出接口,CommonJS 模塊是運(yùn)行時(shí)確定輸出接口,所以采用 import 命令加載 CommonJS 模塊時(shí),不允許采用下面的寫
法。
上面的寫法不正確,因?yàn)?fs 是 CommonJS 格式,只有在運(yùn)行時(shí)才能確定 readfile 接口,而 import 命令要求編譯時(shí)就確定這個(gè)接口。解決方法就是改
為整體輸入。
5CommonJS 模塊加載 ES6 模塊
CommonJS 模塊加載 ES6 模塊,不能使用 require 命令,而要使用 import() 函數(shù)。ES6 模塊的所有輸出接口,會(huì)成為輸入對(duì)象的屬性
// es.mjs let foo = { bar: 'my-default' }; export default foo; foo = null; // cjs.js const es_namespace = await import('./es'); // es_namespace = { // get default() { // ... // } // } console.log(es_namespace.default); // { bar:'my-default' }上面代碼中, default 接口變成了 es_namespace.default 屬性。另外,由于存在緩存機(jī)制, es.js 對(duì) foo 的重新賦值沒有在模塊外部反映出來。
下面是另一個(gè)例子。
6循環(huán)加載
“循環(huán)加載”(circular dependency)指的是, a 腳本的執(zhí)行依賴 b 腳本,而 b 腳本的執(zhí)行又依賴 a 腳本。
// a.js var b = require('b'); // b.js var a = require('a');通常,“循環(huán)加載”表示存在強(qiáng)耦合,如果處理不好,還可能導(dǎo)致遞歸加載,使得程序無(wú)法執(zhí)行,因此應(yīng)該避免出現(xiàn)。
但是實(shí)際上,這是很難避免的,尤其是依賴關(guān)系復(fù)雜的大項(xiàng)目,很容易出現(xiàn) a 依賴 b , b 依賴 c , c 又依賴 a 這樣的情況。這意味著,模塊加載機(jī)制必須考
慮“循環(huán)加載”的情況。
對(duì)于 JavaScript 語(yǔ)言來說,目前最常見的兩種模塊格式 CommonJS 和 ES6,處理“循環(huán)加載”的方法是不一樣的,返回的結(jié)果也不一樣。
6.1CommonJS 模塊的加載原理
介紹 ES6 如何處理“循環(huán)加載”之前,先介紹目前最流行的 CommonJS 模塊格式的加載原理。
CommonJS 的一個(gè)模塊,就是一個(gè)腳本文件。 require 命令第一次加載該腳本,就會(huì)執(zhí)行整個(gè)腳本,然后在內(nèi)存生成一個(gè)對(duì)象
上面代碼就是 Node 內(nèi)部加載模塊后生成的一個(gè)對(duì)象。該對(duì)象的 id 屬性是模塊名, exports 屬性是模塊輸出的各個(gè)接口, loaded 屬性是一個(gè)布爾值,表
示該模塊的腳本是否執(zhí)行完畢。其他還有很多屬性,這里都省略了。
以后需要用到這個(gè)模塊的時(shí)候,就會(huì)到 exports 屬性上面取值。即使再次執(zhí)行 require 命令,也不會(huì)再次執(zhí)行該模塊,而是到緩存之中取值。也就是說,
CommonJS 模塊無(wú)論加載多少次,都只會(huì)在第一次加載時(shí)運(yùn)行一次,以后再加載,就返回第一次運(yùn)行的結(jié)果,除非手動(dòng)清除系統(tǒng)緩存。
6.2CommonJS 模塊的循環(huán)加載
CommonJS 模塊的重要特性是加載時(shí)執(zhí)行,即腳本代碼在 require 的時(shí)候,就會(huì)全部執(zhí)行。一旦出現(xiàn)某個(gè)模塊被"循環(huán)加載",就只輸出已經(jīng)執(zhí)行的部
分,還未執(zhí)行的部分不會(huì)輸出。
讓我們來看,Node 官方文檔里面的例子。腳本文件 a.js 代碼如下。
上面代碼之中, a.js 腳本先輸出一個(gè) done 變量,然后加載另一個(gè)腳本文件 b.js 。注意,此時(shí) a.js 代碼就停在這里,等待 b.js 執(zhí)行完畢,再往下執(zhí)行。
再看 b.js 的代碼。
上面代碼之中, b.js 執(zhí)行到第二行,就會(huì)去加載 a.js ,這時(shí),就發(fā)生了“循環(huán)加載”。系統(tǒng)會(huì)去 a.js 模塊對(duì)應(yīng)對(duì)象的 exports 屬性取值,可是因?yàn)?a.js
還沒有執(zhí)行完,從 exports 屬性只能取回已經(jīng)執(zhí)行的部分,而不是最后的值。
a.js 已經(jīng)執(zhí)行的部分,只有一行。
因此,對(duì)于 b.js 來說,它從 a.js 只輸入一個(gè)變量 done ,值為 false 。
然后, b.js 接著往下執(zhí)行,等到全部執(zhí)行完畢,再把執(zhí)行權(quán)交還給 a.js 。于是, a.js 接著往下執(zhí)行,直到執(zhí)行完畢。我們寫一個(gè)腳本 main.js ,驗(yàn)證這
個(gè)過程。
執(zhí)行 main.js ,運(yùn)行結(jié)果如下。
$ node main.js 在 b.js 之中,a.done = false b.js 執(zhí)行完畢 在 a.js 之中,b.done = true a.js 執(zhí)行完畢 在 main.js 之中, a.done=true, b.done=true上面的代碼證明了兩件事。一是,在 b.js 之中, a.js 沒有執(zhí)行完畢,只執(zhí)行了第一行。二是, main.js 執(zhí)行到第二行時(shí),不會(huì)再次執(zhí)行 b.js ,而是輸出
緩存的 b.js 的執(zhí)行結(jié)果,即它的第四行
總之,CommonJS 輸入的是被輸出值的拷貝,不是引用。
另外,由于 CommonJS 模塊遇到循環(huán)加載時(shí),返回的是當(dāng)前已經(jīng)執(zhí)行的部分的值,而不是代碼全部執(zhí)行后的值,兩者可能會(huì)有差異。所以,輸入變量的
時(shí)候,必須非常小心。
上面代碼中,如果發(fā)生循環(huán)加載, require('a').foo 的值很可能后面會(huì)被改寫,改用 require('a') 會(huì)更保險(xiǎn)一點(diǎn)
7ES6 模塊的循環(huán)加載
ES6 處理“循環(huán)加載”與 CommonJS 有本質(zhì)的不同。ES6 模塊是動(dòng)態(tài)引用,如果使用 import 從一個(gè)模塊加載變量(即 import foo from 'foo' ),那些
變量不會(huì)被緩存,而是成為一個(gè)指向被加載模塊的引用,需要開發(fā)者自己保證,真正取值的時(shí)候能夠取到值。
請(qǐng)看下面這個(gè)例子。
上面代碼中, a.mjs 加載 b.mjs , b.mjs 又加載 a.mjs ,構(gòu)成循環(huán)加載。執(zhí)行 a.mjs ,結(jié)果如下
$ node --experimental-modules a.mjs b.mjs ReferenceError: foo is not defined上面代碼中,執(zhí)行 a.mjs 以后會(huì)報(bào)錯(cuò), foo 變量未定義,這是為什么?
讓我們一行行來看,ES6 循環(huán)加載是怎么處理的。首先,執(zhí)行 a.mjs 以后,引擎發(fā)現(xiàn)它加載了 b.mjs ,因此會(huì)優(yōu)先執(zhí)行 b.mjs ,然后再執(zhí)行 a.js 。接
著,執(zhí)行 b.mjs 的時(shí)候,已知它從 a.mjs 輸入了 foo 接口,這時(shí)不會(huì)去執(zhí)行 a.mjs ,而是認(rèn)為這個(gè)接口已經(jīng)存在了,繼續(xù)往下執(zhí)行。執(zhí)行到第三行
console.log(foo) 的時(shí)候,才發(fā)現(xiàn)這個(gè)接口根本沒定義,因此報(bào)錯(cuò)。
解決這個(gè)問題的方法,就是讓 b.mjs 運(yùn)行的時(shí)候, foo 已經(jīng)有定義了。這可以通過將 foo 寫成函數(shù)來解決。
這時(shí)再執(zhí)行 a.mjs 就可以得到預(yù)期結(jié)果
$ node --experimental-modules a.mjs b.mjs foo a.mjs bar這是因?yàn)楹瘮?shù)具有提升作用,在執(zhí)行 import {bar} from './b' 時(shí),函數(shù) foo 就已經(jīng)有定義了,所以 b.mjs 加載的時(shí)候不會(huì)報(bào)錯(cuò)。這也意味著,如果把函
數(shù) foo 改寫成函數(shù)表達(dá)式,也會(huì)報(bào)錯(cuò)
上面代碼的第四行,改成了函數(shù)表達(dá)式,就不具有提升作用,執(zhí)行就會(huì)報(bào)錯(cuò)。
我們?cè)賮砜?ES6 模塊加載器SystemJS給出的一個(gè)例子
上面代碼中, even.js 里面的函數(shù) even 有一個(gè)參數(shù) n ,只要不等于 0,就會(huì)減去 1,傳入加載的 odd() 。 odd.js 也會(huì)做類似操作。
運(yùn)行上面這段代碼,結(jié)果如下
上面代碼中,參數(shù) n 從 10 變?yōu)?0 的過程中, even() 一共會(huì)執(zhí)行 6 次,所以變量 counter 等于 6。第二次調(diào)用 even() 時(shí),參數(shù) n 從 20 變?yōu)?0,
even() 一共會(huì)執(zhí)行 11 次,加上前面的 6 次,所以變量 counter 等于 17。
這個(gè)例子要是改寫成 CommonJS,就根本無(wú)法執(zhí)行,會(huì)報(bào)錯(cuò)
上面代碼中, even.js 加載 odd.js ,而 odd.js 又去加載 even.js ,形成“循環(huán)加載”。這時(shí),執(zhí)行引擎就會(huì)輸出 even.js 已經(jīng)執(zhí)行的部分(不存在任何結(jié)
果),所以在 odd.js 之中,變量 even 等于 null ,等到后面調(diào)用 even(n-1) 就會(huì)報(bào)錯(cuò)
8ES6 模塊的轉(zhuǎn)碼
瀏覽器目前還不支持 ES6 模塊,為了現(xiàn)在就能使用,可以將轉(zhuǎn)為 ES5 的寫法。除了 Babel 可以用來轉(zhuǎn)碼之外,還有以下兩個(gè)方法,也可以用來轉(zhuǎn)碼
8.1ES6 module transpiler
ES6 module transpiler是 square 公司開源的一個(gè)轉(zhuǎn)碼器,可以將 ES6 模塊轉(zhuǎn)為 CommonJS 模塊或 AMD 模塊的寫法,從而在瀏覽器中使用。
首先,安裝這個(gè)轉(zhuǎn)碼器
然后,使用 compile-modules convert 命令,將 ES6 模塊文件轉(zhuǎn)碼。
$ compile-modules convert file1.js file2.js-o 參數(shù)可以指定轉(zhuǎn)碼后的文件名。
$ compile-modules convert -o out.js file1.js8.2SystemJS
另一種解決方法是使用 SystemJS。它是一個(gè)墊片庫(kù)(polyfill),可以在瀏覽器內(nèi)加載 ES6 模塊、AMD 模塊和 CommonJS 模塊,將其轉(zhuǎn)為 ES5 格
式。它在后臺(tái)調(diào)用的是 Google 的 Traceur 轉(zhuǎn)碼器。
使用時(shí),先在網(wǎng)頁(yè)內(nèi)載入 system.js 文件。
然后,使用 System.import 方法加載模塊文件
<script> System.import('./app.js'); </script>上面代碼中的 ./app ,指的是當(dāng)前目錄下的 app.js 文件。它可以是 ES6 模塊文件, System.import 會(huì)自動(dòng)將其轉(zhuǎn)碼。
需要注意的是, System.import 使用異步加載,返回一個(gè) Promise 對(duì)象,可以針對(duì)這個(gè)對(duì)象編程。下面是一個(gè)模塊文件
然后,在網(wǎng)頁(yè)內(nèi)加載這個(gè)模塊文件
<script> System.import('app/es6-file').then(function(m) { console.log(new m.q().es6); // hello }); </script>上面代碼中, System.import 方法返回的是一個(gè) Promise 對(duì)象,所以可以用 then 方法指定回調(diào)函數(shù)。
總結(jié)
本博客源于本人閱讀相關(guān)書籍和視頻總結(jié),創(chuàng)作不易,謝謝點(diǎn)贊支持。學(xué)到就是賺到。我是歌謠,勵(lì)志成為一名優(yōu)秀的技術(shù)革新人員。
歡迎私信交流,一起學(xué)習(xí),一起成長(zhǎng)。
推薦鏈接 其他文件目錄參照
“睡服“面試官系列之各系列目錄匯總(建議學(xué)習(xí)收藏)
總結(jié)
以上是生活随笔為你收集整理的“睡服”面试官系列第十一篇之module加载实现(建议收藏学习)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机操作系统的图示,电脑操作系统位数的
- 下一篇: 博客搬家工具