CommonJS 模塊的重要特性是加載時執行,即腳本代碼在require
的時候,就會全部執行。一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。
讓我們來看,Node 官方文檔里面的例子。腳本文件a.js
代碼如下。
exports.done = false; var b = require('./b.js'); console.log('在 a.js 之中,b.done = %j', b.done); exports.done = true; console.log('a.js 執行完畢');
上面代碼之中,a.js
腳本先輸出一個done
變量,然后加載另一個腳本文件b.js
。注意,此時a.js
代碼就停在這里,等待b.js
執行完畢,再往下執行。
再看b.js
的代碼。
exports.done = false; var a = require('./a.js'); console.log('在 b.js 之中,a.done = %j', a.done); exports.done = true; console.log('b.js 執行完畢');
上面代碼之中,b.js
執行到第二行,就會去加載a.js
,這時,就發生了“循環加載”。系統會去a.js
模塊對應對象的exports
屬性取值,可是因為a.js
還沒有執行完,從exports
屬性只能取回已經執行的部分,而不是最后的值。
a.js
已經執行的部分,只有一行。
exports.done = false;
因此,對於b.js
來說,它從a.js
只輸入一個變量done
,值為false
。
然后,b.js
接着往下執行,等到全部執行完畢,再把執行權交還給a.js
。於是,a.js
接着往下執行,直到執行完畢。我們寫一個腳本main.js
,驗證這個過程。
var a = require('./a.js'); var b = require('./b.js'); console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
執行main.js
,運行結果如下。
$ node main.js 在 b.js 之中,a.done = false b.js 執行完畢 在 a.js 之中,b.done = true a.js 執行完畢 在 main.js 之中, a.done=true, b.done=true
上面的代碼證明了兩件事。一是,在b.js
之中,a.js
沒有執行完畢,只執行了第一行。二是,main.js
執行到第二行時,不會再次執行b.js
,而是輸出緩存的b.js
的執行結果,即它的第四行。
exports.done = true;
總之,CommonJS 輸入的是被輸出值的拷貝,不是引用。
另外,由於 CommonJS 模塊遇到循環加載時,返回的是當前已經執行的部分的值,而不是代碼全部執行后的值,兩者可能會有差異。所以,輸入變量的時候,必須非常小心。
var a = require('a'); // 安全的寫法 var foo = require('a').foo; // 危險的寫法 exports.good = function (arg) { return a.foo('good', arg); // 使用的是 a.foo 的最新值 }; exports.bad = function (arg) { return foo('bad', arg); // 使用的是一個部分加載時的值 };
上面代碼中,如果發生循環加載,require('a').foo
的值很可能后面會被改寫,改用require('a')
會更保險一點。