瀏覽器和node模塊加載

瀏覽器加載

傳統(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ù)庭砍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末场晶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子怠缸,更是在濱河造成了極大的恐慌诗轻,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揭北,死亡現(xiàn)場離奇詭異扳炬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)搔体,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門恨樟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疚俱,你說我怎么就攤上這事劝术。” “怎么了呆奕?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵养晋,是天一觀的道長。 經(jīng)常有香客問我梁钾,道長绳泉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任陈轿,我火速辦了婚禮圈纺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘麦射。我一直安慰自己蛾娶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布潜秋。 她就那樣靜靜地躺著蛔琅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峻呛。 梳的紋絲不亂的頭發(fā)上罗售,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天辜窑,我揣著相機(jī)與錄音,去河邊找鬼寨躁。 笑死穆碎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的职恳。 我是一名探鬼主播所禀,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼放钦!你這毒婦竟也來了色徘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤操禀,失蹤者是張志新(化名)和其女友劉穎褂策,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颓屑,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斤寂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邢锯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扬蕊。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丹擎,靈堂內(nèi)的尸體忽然破棺而出尾抑,到底是詐尸還是另有隱情,我是刑警寧澤蒂培,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布再愈,位于F島的核電站,受9級特大地震影響护戳,放射性物質(zhì)發(fā)生泄漏翎冲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一媳荒、第九天 我趴在偏房一處隱蔽的房頂上張望抗悍。 院中可真熱鬧,春花似錦钳枕、人聲如沸缴渊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衔沼。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間指蚁,已是汗流浹背菩佑。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凝化,地道東北人稍坯。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像搓劫,于是被迫代替她去往敵國和親劣光。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容