瀏覽器加載
傳統(tǒng)方法
HTML 網(wǎng)頁中,瀏覽器通過<script>標(biāo)簽加載 JavaScript 腳本。
默認(rèn)情況下道川,瀏覽器是同步加載 JavaScript 腳本,即渲染引擎遇到<script>標(biāo)簽就會停下來立宜,等到執(zhí)行完腳本冒萄,再繼續(xù)向下渲染。如果是外部腳本橙数,還必須加入腳本下載的時(shí)間尊流。如果腳本體積很大,下載和執(zhí)行的時(shí)間就會很長灯帮,因此造成瀏覽器堵塞崖技,用戶會感覺到瀏覽器“卡死”了,沒有任何響應(yīng)施流。這顯然是很不好的體驗(yàn)响疚,所以瀏覽器允許腳本異步加載,下面就是兩種異步加載的語法瞪醋。
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
上面代碼中忿晕,<script>標(biāo)簽打開defer或async屬性,腳本就會異步加載银受。渲染引擎遇到這一行命令践盼,就會開始下載外部腳本,但不會等它下載和執(zhí)行宾巍,而是直接執(zhí)行后面的命令咕幻。
defer與async的區(qū)別是:defer要等到整個(gè)頁面在內(nèi)存中正常渲染結(jié)束(DOM 結(jié)構(gòu)完全生成,以及其他腳本執(zhí)行完成)顶霞,才會執(zhí)行肄程;async一旦下載完,渲染引擎就會中斷渲染选浑,執(zhí)行這個(gè)腳本以后蓝厌,再繼續(xù)渲染。一句話古徒,defer是“渲染完再執(zhí)行”拓提,async是“下載完就執(zhí)行”。另外隧膘,如果有多個(gè)defer腳本代态,會按照它們在頁面出現(xiàn)的順序加載寺惫,而多個(gè)async腳本是不能保證加載順序的。
加載規(guī)則
瀏覽器加載 ES6 模塊蹦疑,也使用<script>標(biāo)簽西雀,但是要加入type="module"屬性。
<script type="module" src="./foo.js"></script>
瀏覽器對于帶有type="module"的<script>必尼,都是異步加載蒋搜,不會造成堵塞瀏覽器,即等到整個(gè)頁面渲染完判莉,再執(zhí)行模塊腳本,等同于打開了<script>標(biāo)簽的defer屬性育谬。
如果網(wǎng)頁有多個(gè)<script type="module">券盅,它們會按照在頁面出現(xiàn)的順序依次執(zhí)行。
<script>標(biāo)簽的async屬性也可以打開膛檀,這時(shí)只要加載完成锰镀,渲染引擎就會中斷渲染立即執(zhí)行。執(zhí)行完成后咖刃,再恢復(fù)渲染泳炉。
<script type="module" src="./foo.js" async></script>
一旦使用了async屬性,<script type="module">就不會按照在頁面出現(xiàn)的順序執(zhí)行嚎杨,而是只要該模塊加載完成花鹅,就執(zhí)行該模塊。
對于外部的模塊腳本(上例是foo.js)枫浙,有幾點(diǎn)需要注意刨肃。
代碼是在模塊作用域之中運(yùn)行,而不是在全局作用域運(yùn)行箩帚。模塊內(nèi)部的頂層變量真友,外部不可見。
模塊腳本自動采用嚴(yán)格模式紧帕,不管有沒有聲明use strict盔然。
模塊之中,可以使用import命令加載其他模塊(.js后綴不可省略是嗜,需要提供絕對 URL 或相對 URL)愈案,也可以使用export命令輸出對外接口。
模塊之中叠纷,頂層的this關(guān)鍵字返回undefined刻帚,而不是指向window。也就是說涩嚣,在模塊頂層使用this關(guān)鍵字崇众,是無意義的掂僵。
同一個(gè)模塊如果加載多次,將只執(zhí)行一次顷歌。
ES6 模塊與 CommonJS 模塊的差異
CommonJS 模塊輸出的是一個(gè)值的拷貝锰蓬,ES6 模塊輸出的是值的引用。
CommonJS 模塊是運(yùn)行時(shí)加載眯漩,ES6 模塊是編譯時(shí)輸出接口芹扭。
第二個(gè)差異是因?yàn)?CommonJS 加載的是一個(gè)對象(即module.exports屬性),該對象只有在腳本運(yùn)行完才會生成赦抖。而 ES6 模塊不是對象舱卡,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成队萤。
下面重點(diǎn)解釋第一個(gè)差異轮锥。
CommonJS 模塊輸出的是值的拷貝,也就是說要尔,一旦輸出一個(gè)值舍杜,模塊內(nèi)部的變化就影響不到這個(gè)值。
ES6 模塊的運(yùn)行機(jī)制與 CommonJS 不一樣赵辕。JS 引擎對腳本靜態(tài)分析的時(shí)候既绩,遇到模塊加載命令import,就會生成一個(gè)只讀引用还惠。等到腳本真正執(zhí)行時(shí)饲握,再根據(jù)這個(gè)只讀引用,到被加載的那個(gè)模塊里面去取值吸重。換句話說互拾,ES6 的import有點(diǎn)像 Unix 系統(tǒng)的“符號連接”,原始值變了嚎幸,import加載的值也會跟著變颜矿。因此,ES6 模塊是動態(tài)引用嫉晶,并且不會緩存值骑疆,模塊里面的變量綁定其所在的模塊。
Node 加載
概述
Node 對 ES6 模塊的處理比較麻煩替废,因?yàn)樗凶约旱?CommonJS 模塊格式箍铭,與 ES6 模塊格式是不兼容的。目前的解決方案是椎镣,將兩者分開诈火,ES6 模塊和 CommonJS 采用各自的加載方案。
Node 要求 ES6 模塊采用.mjs后綴文件名状答。也就是說冷守,只要腳本文件里面使用import或者export命令刀崖,那么就必須采用.mjs后綴名。require命令不能加載.mjs文件拍摇,會報(bào)錯亮钦,只有import命令才可以加載.mjs文件。反過來充活,.mjs文件里面也不能使用require命令蜂莉,必須使用import。
目前混卵,這項(xiàng)功能還在試驗(yàn)階段映穗。安裝 Node v8.5.0 或以上版本,要用--experimental-modules參數(shù)才能打開該功能淮菠。
$ node--experimental-modules my-app.mjs
為了與瀏覽器的import加載規(guī)則相同男公,Node 的.mjs文件支持 URL 路徑。
import'./foo?query=1'; // 加載 ./foo 傳入?yún)?shù) ?query=1
上面代碼中合陵,腳本路徑帶有參數(shù)?query=1,Node 會按 URL 規(guī)則解讀澄阳。同一個(gè)腳本只要參數(shù)不同拥知,就會被加載多次,并且保存成不同的緩存碎赢。由于這個(gè)原因低剔,只要文件名中含有:、%肮塞、#襟齿、?等特殊字符,最好對這些字符進(jìn)行轉(zhuǎn)義枕赵。
目前猜欺,Node 的import命令只支持加載本地模塊(file:協(xié)議),不支持加載遠(yuǎn)程模塊拷窜。
如果模塊名不含路徑开皿,那么import命令會去node_modules目錄尋找這個(gè)模塊。
import'baz';import'abc/123';
如果模塊名包含路徑篮昧,那么import命令會按照路徑去尋找這個(gè)名字的腳本文件赋荆。
import 'file:///etc/config/app.json';import'./foo';import'./foo?search';import'../bar';import'/baz';
如果腳本文件省略了后綴名,比如import './foo'懊昨,Node 會依次嘗試四個(gè)后綴名:./foo.mjs窄潭、./foo.js、./foo.json酵颁、./foo.node嫉你。如果這些腳本文件都不存在月帝,Node 就會去加載./foo/package.json的main字段指定的腳本。如果./foo/package.json不存在或者沒有main字段均抽,那么就會依次加載./foo/index.mjs嫁赏、./foo/index.js、./foo/index.json油挥、./foo/index.node潦蝇。如果以上四個(gè)文件還是都不存在,就會拋出錯誤深寥。
最后攘乒,Node 的import命令是異步加載,這一點(diǎn)與瀏覽器的處理方法相同惋鹅。
內(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.jsmodule.exports={__dirname};// use.mjsimport expose from'./expose.js';const{__dirname}=expose;
上面代碼中浪腐,expose.js是一個(gè) CommonJS 模塊纵揍,輸出變量__dirname,該變量在 ES6 模塊之中不存在议街。ES6 模塊加載expose.js泽谨,就可以得到__dirname。
ES6 模塊加載 CommonJS 模塊
CommonJS 模塊的輸出都定義在module.exports這個(gè)屬性上面。Node 的import命令加載 CommonJS 模塊吧雹,Node 會自動將module.exports屬性骨杂,當(dāng)作模塊的默認(rèn)輸出,即等同于export default xxx雄卷。
下面是一個(gè) CommonJS 模塊搓蚪。
// a.jsmodule.exports={foo:'hello',bar:'world'};// 等同于export default{foo:'hello',bar:'world'};
import命令加載上面的模塊,module.exports會被視為默認(rèn)輸出丁鹉,即import命令實(shí)際上輸入的是這樣一個(gè)對象{ default: module.exports }妒潭。
所以,一共有三種寫法揣钦,可以拿到 CommonJS 模塊的module.exports雳灾。
// 寫法一import baz from'./a';// baz = {foo: 'hello', bar: 'world'};// 寫法二import{default as baz}from'./a';// baz = {foo: 'hello', bar: 'world'};// 寫法三import*as baz from'./a';// baz = {//? get default() {return module.exports;},//? get foo() {return this.default.foo}.bind(baz),//? get bar() {return this.default.bar}.bind(baz)// }
上面代碼的第三種寫法,可以通過baz.default拿到module.exports冯凹。foo屬性和bar屬性就是可以通過這種方法拿到了module.exports谎亩。
下面是一些例子。
// b.jsmodule.exports=null;// es.jsimport foo from'./b';// foo = null;import*as bar from'./b';// bar = { default:null };
上面代碼中宇姚,es.js采用第二種寫法時(shí)匈庭,要通過bar.default這樣的寫法,才能拿到module.exports浑劳。
// c.jsmodule.exports=functiontwo(){return2;};// es.jsimport foo from'./c';foo(); // 2import*as bar from'./c';bar.default(); // 2bar(); // throws, bar is not a function
上面代碼中嚎花,bar本身是一個(gè)對象,不能當(dāng)作函數(shù)調(diào)用呀洲,只能通過bar.default調(diào)用。
CommonJS 模塊的輸出緩存機(jī)制啼止,在 ES6 加載方式下依然有效道逗。
// foo.jsmodule.exports=123;setTimeout(_=>module.exports=null);
上面代碼中,對于加載foo.js的腳本献烦,module.exports將一直是123滓窍,而不會變成null。
由于 ES6 模塊是編譯時(shí)確定輸出接口巩那,CommonJS 模塊是運(yùn)行時(shí)確定輸出接口吏夯,所以采用import命令加載 CommonJS 模塊時(shí),不允許采用下面的寫法即横。
// 不正確import{readFile}from'fs';
上面的寫法不正確噪生,因?yàn)閒s是 CommonJS 格式,只有在運(yùn)行時(shí)才能確定readFile接口东囚,而import命令要求編譯時(shí)就確定這個(gè)接口跺嗽。解決方法就是改為整體輸入。
// 正確的寫法一import*as express from'express';const app=express.default();// 正確的寫法二import express from'express';const app=express();
CommonJS 模塊加載 ES6 模塊
CommonJS 模塊加載 ES6 模塊,不能使用require命令桨嫁,而要使用import()函數(shù)植兰。ES6 模塊的所有輸出接口,會成為輸入對象的屬性璃吧。
// es.mjsletfoo={bar:'my-default'};export default foo;// cjs.jsconst es_namespace=awaitimport('./es.mjs');// es_namespace = {//? get default() {//? ? ...//? }// }console.log(es_namespace.default);// { bar:'my-default' }
上面代碼中楣导,default接口變成了es_namespace.default屬性。
下面是另一個(gè)例子畜挨。
// es.jsexportletfoo={bar:'my-default'};export{foo as bar};exportfunctionf(){};export classc{};// cjs.jsconst es_namespace=awaitimport('./es');// es_namespace = {//? get foo() {return foo;}//? get bar() {return foo;}//? get f() {return f;}//? get c() {return c;}// }
循環(huán)加載
“循環(huán)加載”(circular dependency)指的是筒繁,a腳本的執(zhí)行依賴b腳本,而b腳本的執(zhí)行又依賴a腳本朦促。
// a.jsvarb=require('b');// b.jsvara=require('a');
通常膝晾,“循環(huán)加載”表示存在強(qiáng)耦合,如果處理不好务冕,還可能導(dǎo)致遞歸加載血当,使得程序無法執(zhí)行,因此應(yīng)該避免出現(xiàn)禀忆。
但是實(shí)際上臊旭,這是很難避免的,尤其是依賴關(guān)系復(fù)雜的大項(xiàng)目箩退,很容易出現(xiàn)a依賴b离熏,b依賴c,c又依賴a這樣的情況戴涝。這意味著滋戳,模塊加載機(jī)制必須考慮“循環(huán)加載”的情況。
對于 JavaScript 語言來說啥刻,目前最常見的兩種模塊格式 CommonJS 和 ES6奸鸯,處理“循環(huán)加載”的方法是不一樣的,返回的結(jié)果也不一樣可帽。
CommonJS 模塊的加載原理
介紹 ES6 如何處理“循環(huán)加載”之前娄涩,先介紹目前最流行的 CommonJS 模塊格式的加載原理。
CommonJS 的一個(gè)模塊映跟,就是一個(gè)腳本文件蓄拣。require命令第一次加載該腳本,就會執(zhí)行整個(gè)腳本努隙,然后在內(nèi)存生成一個(gè)對象球恤。
{id:'...',exports:{...},loaded:true,...}
上面代碼就是 Node 內(nèi)部加載模塊后生成的一個(gè)對象。該對象的id屬性是模塊名剃法,exports屬性是模塊輸出的各個(gè)接口碎捺,loaded屬性是一個(gè)布爾值路鹰,表示該模塊的腳本是否執(zhí)行完畢。其他還有很多屬性收厨,這里都省略了晋柱。
以后需要用到這個(gè)模塊的時(shí)候,就會到exports屬性上面取值诵叁。即使再次執(zhí)行require命令雁竞,也不會再次執(zhí)行該模塊,而是到緩存之中取值拧额。也就是說碑诉,CommonJS 模塊無論加載多少次,都只會在第一次加載時(shí)運(yùn)行一次侥锦,以后再加載进栽,就返回第一次運(yùn)行的結(jié)果,除非手動清除系統(tǒng)緩存恭垦。
CommonJS 模塊的循環(huán)加載
CommonJS 模塊的重要特性是加載時(shí)執(zhí)行快毛,即腳本代碼在require的時(shí)候,就會全部執(zhí)行番挺。一旦出現(xiàn)某個(gè)模塊被"循環(huán)加載"唠帝,就只輸出已經(jīng)執(zhí)行的部分,還未執(zhí)行的部分不會輸出玄柏。
讓我們來看襟衰,Node?官方文檔里面的例子。腳本文件a.js代碼如下粪摘。
exports.done=false;varb=require('./b.js');console.log('在 a.js 之中瀑晒,b.done = %j',b.done);exports.done=true;console.log('a.js 執(zhí)行完畢');
上面代碼之中,a.js腳本先輸出一個(gè)done變量徘意,然后加載另一個(gè)腳本文件b.js瑰妄。注意,此時(shí)a.js代碼就停在這里映砖,等待b.js執(zhí)行完畢,再往下執(zhí)行灾挨。
再看b.js的代碼邑退。
exports.done=false;vara=require('./a.js');console.log('在 b.js 之中,a.done = %j',a.done);exports.done=true;console.log('b.js 執(zhí)行完畢');
上面代碼之中授滓,b.js執(zhí)行到第二行心褐,就會去加載a.js朗鸠,這時(shí),就發(fā)生了“循環(huán)加載”莫矗。系統(tǒng)會去a.js模塊對應(yīng)對象的exports屬性取值,可是因?yàn)閍.js還沒有執(zhí)行完,從exports屬性只能取回已經(jīng)執(zhí)行的部分作谚,而不是最后的值三娩。
a.js已經(jīng)執(zhí)行的部分,只有一行妹懒。
exports.done=false;
因此雀监,對于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è)過程苔巨。
vara=require('./a.js');varb=require('./b.js');console.log('在 main.js 之中, a.done=%j, b.done=%j',a.done,b.done);
執(zhí)行main.js,運(yùn)行結(jié)果如下废离。
$ node main.js在 b.js 之中侄泽,a.done=falseb.js 執(zhí)行完畢在 a.js 之中,b.done=truea.js 執(zhí)行完畢在 main.js 之中,a.done=true,b.done=true
上面的代碼證明了兩件事蜻韭。一是悼尾,在b.js之中,a.js沒有執(zhí)行完畢肖方,只執(zhí)行了第一行闺魏。二是,main.js執(zhí)行到第二行時(shí)俯画,不會再次執(zhí)行b.js析桥,而是輸出緩存的b.js的執(zhí)行結(jié)果,即它的第四行艰垂。
exports.done=true;
總之泡仗,CommonJS 輸入的是被輸出值的拷貝,不是引用猜憎。
另外娩怎,由于 CommonJS 模塊遇到循環(huán)加載時(shí),返回的是當(dāng)前已經(jīng)執(zhí)行的部分的值胰柑,而不是代碼全部執(zhí)行后的值截亦,兩者可能會有差異爬泥。所以,輸入變量的時(shí)候崩瓤,必須非常小心袍啡。
vara=require('a'); // 安全的寫法varfoo=require('a').foo; // 危險(xiǎn)的寫法exports.good=function(arg){returna.foo('good',arg); // 使用的是 a.foo 的最新值};exports.bad=function(arg){returnfoo('bad',arg); // 使用的是一個(gè)部分加載時(shí)的值};
上面代碼中,如果發(fā)生循環(huán)加載谷遂,require('a').foo的值很可能后面會被改寫葬馋,改用require('a')會更保險(xiǎn)一點(diǎn)。
ES6 模塊的循環(huán)加載
ES6 處理“循環(huán)加載”與 CommonJS 有本質(zhì)的不同肾扰。ES6 模塊是動態(tài)引用畴嘶,如果使用import從一個(gè)模塊加載變量(即import foo from 'foo'),那些變量不會被緩存集晚,而是成為一個(gè)指向被加載模塊的引用窗悯,需要開發(fā)者自己保證,真正取值的時(shí)候能夠取到值偷拔。
請看下面這個(gè)例子蒋院。
// a.mjsimport{bar}from'./b';console.log('a.mjs');console.log(bar);exportletfoo='foo';// b.mjsimport{foo}from'./a';console.log('b.mjs');console.log(foo);exportletbar='bar';
上面代碼中,a.mjs加載b.mjs莲绰,b.mjs又加載a.mjs欺旧,構(gòu)成循環(huán)加載。執(zhí)行a.mjs蛤签,結(jié)果如下辞友。
$ node--experimental-modules a.mjsb.mjsReferenceError:foo is not defined
上面代碼中,執(zhí)行a.mjs以后會報(bào)錯震肮,foo變量未定義称龙,這是為什么?
讓我們一行行來看戳晌,ES6 循環(huán)加載是怎么處理的鲫尊。首先,執(zhí)行a.mjs以后沦偎,引擎發(fā)現(xiàn)它加載了b.mjs疫向,因此會優(yōu)先執(zhí)行b.mjs,然后再執(zhí)行a.mjs豪嚎。接著鸿捧,執(zhí)行b.mjs的時(shí)候,已知它從a.mjs輸入了foo接口疙渣,這時(shí)不會去執(zhí)行a.mjs,而是認(rèn)為這個(gè)接口已經(jīng)存在了堆巧,繼續(xù)往下執(zhí)行妄荔。執(zhí)行到第三行console.log(foo)的時(shí)候泼菌,才發(fā)現(xiàn)這個(gè)接口根本沒定義,因此報(bào)錯啦租。
解決這個(gè)問題的方法哗伯,就是讓b.mjs運(yùn)行的時(shí)候,foo已經(jīng)有定義了篷角。這可以通過將foo寫成函數(shù)來解決焊刹。
// a.mjsimport{bar}from'./b';console.log('a.mjs');console.log(bar());functionfoo(){return'foo'}export{foo};// b.mjsimport{foo}from'./a';console.log('b.mjs');console.log(foo());functionbar(){return'bar'}export{bar};
這時(shí)再執(zhí)行a.mjs就可以得到預(yù)期結(jié)果。
$ node--experimental-modules a.mjsb.mjsfooa.mjsbar
這是因?yàn)楹瘮?shù)具有提升作用恳蹲,在執(zhí)行import {bar} from './b'時(shí)虐块,函數(shù)foo就已經(jīng)有定義了,所以b.mjs加載的時(shí)候不會報(bào)錯嘉蕾。這也意味著贺奠,如果把函數(shù)foo改寫成函數(shù)表達(dá)式,也會報(bào)錯错忱。
// a.mjsimport{bar}from'./b';console.log('a.mjs');console.log(bar());const foo=()=>'foo';export{foo};
上面代碼的第四行儡率,改成了函數(shù)表達(dá)式,就不具有提升作用以清,執(zhí)行就會報(bào)錯儿普。
我們再來看 ES6 模塊加載器SystemJS給出的一個(gè)例子。
// even.jsimport{odd}from'./odd'exportvarcounter=0;exportfunctioneven(n){counter++;returnn===0||odd(n-1);}// odd.jsimport{even}from'./even';exportfunctionodd(n){returnn!==0&&even(n-1);}
上面代碼中掷倔,even.js里面的函數(shù)even有一個(gè)參數(shù)n眉孩,只要不等于 0,就會減去 1今魔,傳入加載的odd()勺像。odd.js也會做類似操作。
運(yùn)行上面這段代碼错森,結(jié)果如下吟宦。
$ babel-node>import*as m from'./even.js';>m.even(10);true>m.counter6>m.even(20)true>m.counter17
上面代碼中,參數(shù)n從 10 變?yōu)?0 的過程中涩维,even()一共會執(zhí)行 6 次殃姓,所以變量counter等于 6。第二次調(diào)用even()時(shí)瓦阐,參數(shù)n從 20 變?yōu)?0蜗侈,even()一共會執(zhí)行 11 次,加上前面的 6 次睡蟋,所以變量counter等于 17踏幻。
這個(gè)例子要是改寫成 CommonJS,就根本無法執(zhí)行戳杀,會報(bào)錯该面。
// even.jsvarodd=require('./odd');varcounter=0;exports.counter=counter;exports.even=function(n){counter++;returnn==0||odd(n-1);}// odd.jsvareven=require('./even').even;module.exports=function(n){returnn!=0&&even(n-1);}
上面代碼中夭苗,even.js加載odd.js,而odd.js又去加載even.js隔缀,形成“循環(huán)加載”题造。這時(shí),執(zhí)行引擎就會輸出even.js已經(jīng)執(zhí)行的部分(不存在任何結(jié)果)猾瘸,所以在odd.js之中界赔,變量even等于undefined,等到后面調(diào)用even(n - 1)就會報(bào)錯牵触。
$ node>var m=require('./even');>m.even(10)TypeError:even is not afunction
ES6 模塊的轉(zhuǎn)碼
瀏覽器目前還不支持 ES6 模塊淮悼,為了現(xiàn)在就能使用,可以將其轉(zhuǎn)為 ES5 的寫法荒吏。除了 Babel 可以用來轉(zhuǎn)碼之外敛惊,還有以下兩個(gè)方法,也可以用來轉(zhuǎn)碼绰更。
ES6 module transpiler
ES6 module transpiler是 square 公司開源的一個(gè)轉(zhuǎn)碼器瞧挤,可以將 ES6 模塊轉(zhuǎn)為 CommonJS 模塊或 AMD 模塊的寫法,從而在瀏覽器中使用儡湾。
首先特恬,安裝這個(gè)轉(zhuǎn)碼器。
$ npm install-g es6-module-transpiler
然后徐钠,使用compile-modules convert命令癌刽,將 ES6 模塊文件轉(zhuǎn)碼。
$ compile-modules convert file1.js file2.js
-o參數(shù)可以指定轉(zhuǎn)碼后的文件名尝丐。
$ compile-modules convert-o out.js file1.js
SystemJS
另一種解決方法是使用?SystemJS显拜。它是一個(gè)墊片庫(polyfill),可以在瀏覽器內(nèi)加載 ES6 模塊爹袁、AMD 模塊和 CommonJS 模塊远荠,將其轉(zhuǎn)為 ES5 格式。它在后臺調(diào)用的是 Google 的 Traceur 轉(zhuǎn)碼器失息。
使用時(shí)譬淳,先在網(wǎng)頁內(nèi)載入system.js文件。
<script src="system.js"></script>
然后盹兢,使用System.import方法加載模塊文件邻梆。
<script>
? System.import('./app.js');
</script>
上面代碼中的./app,指的是當(dāng)前目錄下的 app.js 文件绎秒。它可以是 ES6 模塊文件浦妄,System.import會自動將其轉(zhuǎn)碼。
需要注意的是,System.import使用異步加載剂娄,返回一個(gè) Promise 對象窘问,可以針對這個(gè)對象編程。下面是一個(gè)模塊文件宜咒。
// app/es6-file.js:export classq{constructor(){this.es6='hello';}}
然后,在網(wǎng)頁內(nèi)加載這個(gè)模塊文件把鉴。
<script>
System.import('app/es6-file').then(function(m) {
? console.log(new m.q().es6); // hello
});
</script>
上面代碼中故黑,System.import方法返回的是一個(gè) Promise 對象,所以可以用then方法指定回調(diào)函數(shù)庭砍。