您好,歡迎來電子發(fā)燒友網(wǎng)! ,新用戶?[免費(fèi)注冊(cè)]

您的位置:電子發(fā)燒友網(wǎng)>源碼下載>數(shù)值算法/人工智能>

Module的加載實(shí)現(xiàn)

大小:0.3 MB 人氣: 2017-09-28 需要積分:1

  本文主要介紹如何在瀏覽器和Node之中加載ES6模塊,以及實(shí)際開發(fā)中經(jīng)常遇到的一些問題(比如循環(huán)加載)。

  瀏覽器加載

  傳統(tǒng)方法

  在HTML網(wǎng)頁中,瀏覽器通過

  《!-- 頁面內(nèi)嵌的腳本 --》《type=“application/java”》// module code《/》《!-- 外部腳本 --》《type=“application/java”src=“path/to/myModule.js”》《/》

  上面的代碼中,由于瀏覽器腳本的默認(rèn)語言是Java,因此type=”application/ java”可以省略。

  默認(rèn)情況下,瀏覽器同步加載Java腳本,即渲染引擎遇到《》標(biāo)簽就會(huì)停下來,等到腳本執(zhí)行完畢再繼續(xù)向下渲染。如果是外部腳本,還必須加入腳本下載的時(shí)間。

  如果腳本體積很大,下載和執(zhí)行的時(shí)間就會(huì)很長,因此造成瀏覽器堵塞,用戶會(huì)感覺到瀏覽器“卡死”了,沒有任何響應(yīng)。這顯然是很不好的體驗(yàn),所以瀏覽器允許腳本異步加載,下面就是兩種異步加載的語法。

  《src=“path/to/myModule.js”defer》《/》《src=“path/to/myModule.js”async》《/》

  上面的代碼中,《》標(biāo)簽打開defer或async屬性,腳本就會(huì)異步加載。渲染引擎遇到這一行命令就會(huì)開始下載外部腳本,但不會(huì)等它下載和執(zhí)行,而是直接執(zhí)行后面的命令。

  defer與async的區(qū)別是,前者要等到整個(gè)頁面正常渲染結(jié)束才會(huì)執(zhí)行,而后者一旦下載完成,渲染引擎就會(huì)中斷渲染,執(zhí)行這個(gè)腳本以后再繼續(xù)渲染。用一句話來說,defer是“渲染完再執(zhí)行”,async是“下載完就執(zhí)行”。另外,如果有多個(gè)defer腳本,則會(huì)按照它們?cè)陧撁娉霈F(xiàn)的順序加載,而多個(gè)async腳本是不能保證加載順序的。

  加載規(guī)則

  瀏覽器加載ES6模塊時(shí)也使用《》標(biāo)簽,但是要加入type=”module”屬性。

  《type=“module”src=“foo.js”》《/》

  上面的代碼在網(wǎng)頁中插入了一個(gè)模塊foo.js,由于type屬性設(shè)為module,所以瀏覽器知道這是一個(gè)ES6模塊。

  對(duì)于帶有type=”module”的《》,瀏覽器都是異步加載的,不會(huì)造成瀏覽器堵塞,即等到整個(gè)頁面渲染完再執(zhí)行模塊腳本,等同于打開了《》標(biāo)簽的defer屬性。

  《type=“module”src=“foo.js”》《/》《!-- 等同于 --》《type=“module”src=“foo.js”defer》《/》

  《》標(biāo)簽的async屬性也可以打開,這時(shí)只要加載完成,渲染引擎就會(huì)中斷渲染立即執(zhí)行。執(zhí)行完成后,再恢復(fù)渲染。

  《type=“module”src=“foo.js”async》《/》

  ES6模塊也允許內(nèi)嵌在網(wǎng)頁中,語法行為與加載外部腳本完全一致。

  《type=“module”》import utils from “。/utils.js”; // other code《/》

  對(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)鍵字是無意義的。

  同一個(gè)模塊如果加載多次,將只執(zhí)行一次。

  下面是一個(gè)示例模塊。

  importutils from ‘https://example.com/js/utils.js’; constx = 1; console.log(x === window.x);//falseconsole.log( this=== undefined); //truedeletex; //句法錯(cuò)誤,嚴(yán)格模式禁止刪除變量

  利用頂層的this等于undefined這個(gè)語法點(diǎn),可以監(jiān)測(cè)當(dāng)前代碼是否在ES6模塊之中。

  constisNotModule = this!== undefined;ES6模塊與CommonJS模塊的差異

  討論Node加載ES6模塊之前,必須了解ES6模塊與CommonJS模塊的差異,具體的兩大差異如下。

  CommonJS模塊輸出的是一個(gè)值的復(fù)制,ES6模塊輸出的是值的引用。

  CommonJS模塊是運(yùn)行時(shí)加載,ES6模塊是編譯時(shí)輸出接口。

  第二個(gè)差異是因?yàn)镃ommonJS加載的是一個(gè)對(duì)象(即module.exports屬性),該對(duì)象只有在腳本運(yùn)行結(jié)束時(shí)才會(huì)生成。而ES6模塊不是對(duì)象,它的對(duì)外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會(huì)生成。

  下面重點(diǎn)解釋第一個(gè)差異。

  CommonJS模塊輸出的是值的復(fù)制,也就是說,一旦輸出一個(gè)值,模塊內(nèi)部的變化就影響不到這個(gè)值。請(qǐng)看下面這個(gè)模塊文件lib.js的例子。

  // lib.jsvarcounter = 3; functionincCounter(){counter++; } module.exports = { counter: counter, incCounter: incCounter, };

  上面的代碼輸出內(nèi)部變量counter和改寫這個(gè)變量的內(nèi)部方法incCounter。然后,在main.js里面加載這個(gè)模塊。

  // main.jsvar mod= require( ‘。/lib’); console. log( mod.counter); // 3mod.incCounter(); console. log( mod.counter); // 3

  上面的代碼說明,lib.js模塊加載以后,它的內(nèi)部變化就影響不到輸出的mod.counter了。這是因?yàn)閙od.counter是一個(gè)原始類型的值,會(huì)被緩存。除非寫成一個(gè)函數(shù),否則得到內(nèi)部變動(dòng)后的值。

  // lib.jsvarcounter = 3; functionincCounter(){counter++; } module.exports = { getcounter() {returncounter }, incCounter: incCounter, };

  上面的代碼中,輸出的counter屬性實(shí)際上是一個(gè)取值器函數(shù)。現(xiàn)在再執(zhí)行main.js就可以正確讀取內(nèi)部變量counter的變動(dòng)了。

  $ node main.js 34

  ES6模塊的運(yùn)行機(jī)制與CommonJS不一樣。JS引擎對(duì)腳本靜態(tài)分析的時(shí)候,遇到模塊加載命令import就會(huì)生成一個(gè)只讀引用。等到腳本真正執(zhí)行時(shí),再根據(jù)這個(gè)只讀引用到被加載的模塊中取值。換句話說,ES6的import有點(diǎn)像Unix系統(tǒng)的“符號(hào)連接”,原始值變了,import加載的值也會(huì)跟著變。因此,ES6模塊是動(dòng)態(tài)引用,并且不會(huì)緩存值,模塊里面的變量綁定其所在的模塊。

  還是以上面的代碼為例。

  // lib.jsexportletcounter = 3; exportfunction incCounter() { counter++; } // main.jsimport { counter, incCounter } from ‘。/lib’; console. log(counter); // 3incCounter(); console.log(counter); // 4

  上面的代碼說明,ES6模塊輸入的變量counter是活的,完全反應(yīng)其所在模塊lib.js內(nèi)部的變化。

  再舉一個(gè)出現(xiàn)在export一節(jié)中的例子。

  //m1.js exportvarfoo = ‘bar’; setTimeout (() =》 foo = ‘baz’, 500); // m2.jsimport{foo}from‘。/m1.js’; console.log(foo); setTimeout(() =》 console.log(foo), 500);

  上面的代碼中,m1.js的變量foo在剛加載時(shí)是bar,過了500ms又變?yōu)閎az。

  來看一下m2.js能否正確讀取這個(gè)變化。

  $ babel-node m2.js bar baz

  上面的代碼表明,ES6模塊不會(huì)緩存運(yùn)行結(jié)果,而是動(dòng)態(tài)地去被加載的模塊取值,并且變量總是綁定其所在的模塊。

  由于ES6輸入的模塊變量只是一個(gè)“符號(hào)連接”,所以這個(gè)變量是只讀的,對(duì)它進(jìn)行重新賦值會(huì)報(bào)錯(cuò)。

  // lib.jsexport letobj = {}; // main.jsimport { obj } from‘。/lib’; obj.prop = 123; // OKobj = {}; // TypeError

  上面的代碼中,main.js從lib.js輸入變量obj,可以對(duì)obj添加屬性,但是重新賦值就會(huì)報(bào)錯(cuò)。因?yàn)樽兞縪bj指向的地址是只讀的,不能重新賦值,這就好比main.js創(chuàng)造了一個(gè)名為obj的const變量。

  最后,export通過接口輸出的是同一個(gè)值。不同的腳本加載這個(gè)接口得到的都是同樣的實(shí)例。

  // mod.jsfunctionC(){this.sum = 0; this.add = function(){this.sum += 1; }; this.show =function(){console.log( this.sum); }; } export letc = newC();

  上面的腳本mod.js輸出的是一個(gè)C的實(shí)例。不同的腳本加載這個(gè)模塊得到的都是同一個(gè)實(shí)例。

  // x.jsimport{c} from ‘。/mod’; c.add(); // y.jsimport{c} from ‘。/mod’; c.show(); // main.jsimport‘。/x’; import‘。/y’;

  現(xiàn)在執(zhí)行main.js,輸出的是1。

  $ babel-node main.js 1

  這就證明了x.js和y.js加載的都是C的同一個(gè)實(shí)例。

  Node加載

  概述

  Node對(duì)ES6模塊的處理比較麻煩,因?yàn)樗凶约旱腃ommonJS模塊格式,與ES6模塊格式是不兼容的。目前的解決方案是,將兩者分開,ES6模塊和CommonJS采用各自的加載方案。

  在靜態(tài)分析階段,一個(gè)模塊腳本只要有一行import或export語句,Node就會(huì)認(rèn)為該腳本為ES6模塊,否則就為CommonJS模塊。如果不輸出任何接口,但是希望被Node認(rèn)為是ES6模塊,可以在腳本中加上如下語句。

  export{};

  上面的命令并不是輸出一個(gè)空對(duì)象,而是不輸出任何接口的ES6標(biāo)準(zhǔn)寫法。

  如果不指定絕對(duì)路徑,Node加載ES6模塊會(huì)依次尋找以下腳本,與require()的規(guī)則一致。

  import‘。/foo’; // 依次尋找// 。/foo.js// 。/foo/package.json// 。/foo/index.jsimport‘baz’; // 依次尋找// 。/node_modules/baz.js// 。/node_modules/baz/package.json// 。/node_modules/baz/index.js// 尋找上一級(jí)目錄// 。。/node_modules/baz.js// 。。/node_modules/baz/package.json// 。。/node_modules/baz/index.js// 再上一級(jí)目錄

  ES6模塊之中,頂層的this指向undefined,CommonJS模塊的頂層this指向當(dāng)前模塊,這是兩者的一個(gè)重大差異。

  import命令加載CommonJS模塊

  Node采用CommonJS模塊格式,模塊的輸出都定義在module.exports屬性上面。在Node環(huán)境中,使用import命令加載CommonJS模塊,Node會(huì)自動(dòng)將module.exports屬性當(dāng)作模塊的默認(rèn)輸出,即等同于export default。

  下面是一個(gè)CommonJS模塊。

  //a.js module. exports= { foo: ‘hello’, bar: ‘world’}; //等同于 exportdefault{ foo: ‘hello’, bar:‘world’};

  import命令加載上面的模塊,module.exports會(huì)被視為默認(rèn)輸出。

  // 寫法一 importbaz from‘。/a’; // baz = {foo: ‘hello’, bar: ‘world’}; // 寫法二 import{defaultasbaz} from‘。/a’; // baz = {foo: ‘hello’, bar: ‘world’};

  如果采用整體輸入的寫法(import * as xxx from someModule),default會(huì)取代module.exports作為輸入的接口。

  import* as baz from ‘。/a’; //baz = { //get default() { returnmodule. exports;}, //get foo() {returnthis. default.foo}.bind(baz), //get bar() { returnthis. default.bar}.bind(baz) //}

  上面的代碼中,this.default取代了module.exports。需要注意的是,Node會(huì)自動(dòng)為baz添加default屬性,通過baz.default獲取module.exports。

  // b.jsmodule.exports = null; // es.jsimport foo from‘。/b’; // foo = null;import * asbar from‘。/b’;// bar = {default:null};

  上面的代碼中,es.js采用第二種寫法時(shí),要通過bar.default這樣的寫法才能獲取module.exports。

  下面是另一個(gè)例子。

  // c.jsmodule.exports = functiontwo(){ return 2; };// es.jsimport foo from‘。/c’; foo(); // 2import * asbar from‘。/c’; bar. default(); // 2bar(); // throws, bar is not a function

  上面的代碼中,bar本身是一個(gè)對(duì)象,不能當(dāng)作函數(shù)調(diào)用,只能通過bar.default調(diào)用。

  CommonJS模塊的輸出緩存機(jī)制在ES6加載方式下依然有效。

  //foo.js module. exports= 123; setTimeout(_ =》module. exports= null);

  上面的代碼中,對(duì)于加載foo.js的腳本,module.exports將一直是123,而不會(huì)變成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 * asexpress from‘express’; constapp = express. default(); import expressfrom‘express’; constapp = express();

  require命令加載ES6模塊

  采用require命令加載ES6模塊時(shí),ES6模塊的所有輸出接口都會(huì)成為輸入對(duì)象的屬性。

  //es.js letfoo = { bar: ‘my-default’}; exportdefaultfoo; foo = null; //cjs.js constes_namespace = require( ‘。/es’); console.log(es_namespace. default); //{ bar: ‘my-default’}

  上面的代碼中,default接口變成了es_namespace.default屬性。另外,由于存在緩存機(jī)制,es.js對(duì)foo的重新賦值沒有在模塊外部反映出來。

  下面是另一個(gè)例子。

  // es.jsexport let foo = {bar: ‘my-default’}; export {foo asbar}; export functionf(){}; exportclassc{}; // cjs.jsconstes_namespace = require( ‘。/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(chǎn)’);

  通常,“循環(huán)加載”表示存在強(qiáng)耦合,如果處理不好,還可能導(dǎo)致遞歸加載,使得程序無法執(zhí)行,因此應(yīng)該避免出現(xiàn)這種現(xiàn)象。

  但是實(shí)際上,這是很難避免的,尤其是依賴關(guān)系復(fù)雜的大項(xiàng)目中很容易出現(xiàn)a依賴b,b依賴c,c又依賴a這樣的情況。這意味著,模塊加載機(jī)制必須考慮“循環(huán)加載”的情況。

  對(duì)于Java語言來說,目前最常見的兩種模塊格式CommonJS和ES6在處理“循環(huán)加載”時(shí)的方法是不一樣的,返回的結(jié)果也不一樣。

  CommonJS模塊的加載原理

  介紹ES6如何處理“循環(huán)加載”之前,先介紹目前最流行的CommonJS模塊格式的加載原理。

  CommonJS的一個(gè)模塊就是一個(gè)腳本文件。require命令第一次加載該腳本時(shí)就會(huì)執(zhí)行整個(gè)腳本,然后在內(nèi)存中生成一個(gè)對(duì)象。

  { id: ‘。。.’, exports: { 。。.}, loaded: true, 。。.}

  上面的代碼就是Node內(nèi)部加載模塊后生成的一個(gè)對(duì)象。該對(duì)象的id屬性是模塊名,exports屬性是模塊輸出的各個(gè)接口,loaded屬性是一個(gè)布爾值,表示該模塊的腳本是否執(zhí)行完畢。其他還有很多屬性,這里都省略了。

  以后需要用到這個(gè)模塊時(shí)就會(huì)到exports屬性上面取值。即使再次執(zhí)行require命令,也不會(huì)再次執(zhí)行該模塊,而是到緩存之中取值。也就是說,CommonJS模塊無論加載多少次,都只會(huì)在第一次加載時(shí)運(yùn)行一次,以后再加載時(shí)就返回第一次運(yùn)行的結(jié)果,除非手動(dòng)清除系統(tǒng)緩存。

  CommonJS模塊的循環(huán)加載

  CommonJS模塊的重要特性是加載時(shí)執(zhí)行,即腳本代碼在require的時(shí)候就會(huì)全部執(zhí)行。一旦出現(xiàn)某個(gè)模塊被“循環(huán)加載”,就只輸出已經(jīng)執(zhí)行的部分,還未執(zhí)行的部分不會(huì)輸出。

  讓我們來看一下Node官方文檔(nodejs.org/api/modules.html#modules_cycles)里面的例子。腳本文件a.js代碼如下。

  exports.done = false; varb = require( ‘。/b.js’); console.log( ‘在 a.js 之中,b.done = %j’, b.done); exports.done = true; console.log( ‘a(chǎn).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í)行到第二行就會(huì)加載a.js,這時(shí)就發(fā)生了“循環(huán)加載”,系統(tǒng)會(huì)去a.js模塊對(duì)應(yīng)對(duì)象的exports屬性中取值,可是因?yàn)閍.js還沒有執(zhí)行完,因此從exports屬性中只能取回已經(jīng)執(zhí)行的部分,而不是最后的值。

  a.js已經(jīng)執(zhí)行的部分只有以下一行。

  exports. done= false;

  因此,對(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è)過程。

  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 = 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í)行了第一行。第二,reimain.js執(zhí)行到第二行時(shí)不會(huì)再次執(zhí)行b.js,而是輸出緩存的b.js的執(zhí)行結(jié)果,即它的第四行。

  exports. done= true;

  總之,CommonJS輸入的是被輸出值的復(fù)制,而不是引用。

  另外,由于CommonJS模塊遇到循環(huán)加載時(shí)返回的是當(dāng)前已經(jīng)執(zhí)行的部分的值,而不是代碼全部執(zhí)行后的值,兩者可能會(huì)有差異。所以,輸入變量的時(shí)候必須非常小心。

  vara = require( ‘a(chǎn)’); // 安全的寫法varfoo = require( ‘a(chǎn)’).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(chǎn)’).foo的值很可能會(huì)被改寫,改用require(‘a(chǎn)’)會(huì)更保險(xiǎn)一點(diǎn)。

  ES6模塊的循環(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.js如下import {bar} from ‘。/b.js’; console. log( ‘a(chǎn).js’); console. log(bar); exportletfoo =‘foo’; // b.jsimport {foo} from ‘。/a.js’; console. log( ‘b.js’); console. log(foo); exportletbar =‘bar’;

  上面的代碼中,a.js加載b.js,b.js又加載a.js,構(gòu)成循環(huán)加載。執(zhí)行a.js,結(jié)果如下。

  $ babel-node a .jsb .jsundefined a .jsBar

  上面的代碼中,由于a.js的第一行是加載b.js,所以先執(zhí)行的是b.js。而b.js的第一行又是加載a.js,這時(shí)由于a.js已經(jīng)開始執(zhí)行,所以不會(huì)重復(fù)執(zhí)行,而是繼續(xù)執(zhí)行b.js,因此第一行輸出的是b.js。

  接著,b.js要打印變量foo,這時(shí)a.js還沒有執(zhí)行完,取不到foo的值,因此打印出來的是undefined。b.js執(zhí)行完便會(huì)開始執(zhí)行a.js,這時(shí)便會(huì)一切正常。

  再來看一個(gè)稍微復(fù)雜的例子(摘自 Axel Rauschmayer 的Exploring ES6,具體內(nèi)容請(qǐng)查看exploringjs.com/es6/ch_modules.html)。

  // a.jsimport{bar} from ‘。/b.js’;export functionfoo(){console.log( ‘foo’); bar(); console.log( ‘執(zhí)行完畢’); } foo(); // b.jsimport{foo} from ‘。/a.js’;export functionbar(){console.log( ‘bar’);if(Math.random() 》 0.5) { foo(); } }

  按照CommonJS規(guī)范,上面的代碼是無法執(zhí)行的。a先加載b,然后b又加載a,這時(shí)a還沒有任何執(zhí)行結(jié)果,所以輸出結(jié)果為null,即對(duì)于b.js來說,變量foo的值等于null,后面的foo()就會(huì)報(bào)錯(cuò)。

  但是,ES6可以執(zhí)行上面的代碼。

  $ babel-node a.js foo bar 執(zhí)行完畢 // 執(zhí)行結(jié)果也有可能是foo bar foo bar 執(zhí)行完畢 執(zhí)行完畢

  上面的代碼中,a.js之所以能夠執(zhí)行,原因就在于ES6加載的變量都是動(dòng)態(tài)引用其所在模塊的。只要引用存在,代碼就能執(zhí)行。

  下面,我們來詳細(xì)分析這段代碼的運(yùn)行過程。

  // a.js// 這一行建立一個(gè)引用,// 從`b.js`引用`bar`import{bar} from ‘。/b.js’;exportfunctionfoo(){// 執(zhí)行時(shí)第一行輸出fooconsole.log( ‘foo’); // 到 b.js 執(zhí)行 barbar(); console.log( ‘執(zhí)行完畢’); } foo(); // b.js// 建立`a.js`的`foo`引用import{foo} from ‘。/a.js’;exportfunctionbar(){// 執(zhí)行時(shí),第二行輸出barconsole.log( ‘bar’); // 遞歸執(zhí)行foo,一旦隨機(jī)數(shù)// 小于等于0.5,就停止執(zhí)行if(Math.random() 》 0.5) { foo(); } }

  再來看ES6模塊加載器SystemJS(github.com/ModuleLoader/es6-module-loader/blob/master/ docs/circular-references-bindings.md)給出的一個(gè)例子。

  // even.jsimport{ odd } from ‘。/odd’ export var counter = 0;export functioneven(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,只要該參數(shù)不等于0,結(jié)果就會(huì)減1,傳入加載的odd()。odd.js也會(huì)進(jìn)行類似操作。

  運(yùn)行上面這段代碼,結(jié)果如下。

  $ babel-node 》 import * asm from‘。/even.js’; 》 m.even( 10); true》 m.counter 6》 m.even(20) true》 m.counter 17

  上面的代碼中,參數(shù)n從10變?yōu)?的過程中,even()一共會(huì)執(zhí)行6次,所以變量counter等于6。第二次調(diào)用even()時(shí),參數(shù)n從20變?yōu)?,even()一共會(huì)執(zhí)行11次,加上前面的6次,所以變量counter等于17。

  這個(gè)例子要是改寫成CommonJS,則會(huì)報(bào)錯(cuò),根本無法執(zhí)行。

  // 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í)行引擎就會(huì)輸出even.js已經(jīng)執(zhí)行的部分(不存在任何結(jié)果),所以在odd.js之中,變量even等于null,后面再調(diào)用even(n-1)就會(huì)報(bào)錯(cuò)。

  $ node 》 varm = require( ‘。/even’); 》 m.even( 10) TypeError: even isnota functionES6模塊的轉(zhuǎn)碼

  瀏覽器目前還不支持ES6模塊,為了實(shí)現(xiàn)立刻使用,我們可以將其轉(zhuǎn)為ES5的寫法。除了Babel可以用來轉(zhuǎn)碼,還有以下兩個(gè)方法也可以用來轉(zhuǎn)碼。

  ES6 module transpiler

  ES6 module transpiler(github.com/esnext/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 .jsfile2 .js```

  -o參數(shù)可以指定轉(zhuǎn)碼后的文件名。

  $ compile-modules convert -o out.jsfile1 .js

  SystemJS

  第二種解決方法使用了SystemJS(github.com/systemjs/systemjs)。它是一個(gè)墊片庫(polyfill),可以在瀏覽器內(nèi)加載ES6模塊、AMD模塊和CommonJS模塊,將其轉(zhuǎn)為ES5格式。它在后臺(tái)調(diào)用的是Google的Traceur轉(zhuǎn)碼器。

  使用時(shí),先在網(wǎng)頁內(nèi)載入system.js文件。

  《src=“system.js”》《/》

  然后,使用System.import方法加載模塊文件。

  《》System.import(‘。/app.js’); 《/》```

  上面代碼中的。/app指的是當(dāng)前目錄下的app.js文件。它可以是ES6模塊文件,System.import會(huì)自動(dòng)將其轉(zhuǎn)碼。

  需要注意的是,System.import使用異步加載,返回一個(gè)Promise對(duì)象,可以針對(duì)這個(gè)對(duì)象編程。下面是一個(gè)模塊文件。

  // app/es6-file.js:export classq{constructor() { this.es6 = ‘hello’; } }

  然后,在網(wǎng)頁內(nèi)加載這個(gè)模塊文件。

  《》System.import(‘a(chǎn)pp/es6-file’).then(function(m){console.log(newm.q().es6); // hello}); 《/》

  上面的代碼中,System.import方法返回的是一個(gè)Promise對(duì)象,所以可以用then方法指定回調(diào)函數(shù)。

非常好我支持^.^

(0) 0%

不好我反對(duì)

(0) 0%

Module的加載實(shí)現(xiàn)下載

相關(guān)電子資料下載

      發(fā)表評(píng)論

      用戶評(píng)論
      評(píng)價(jià):好評(píng)中評(píng)差評(píng)

      發(fā)表評(píng)論,獲取積分! 請(qǐng)遵守相關(guān)規(guī)定!

      ?