Node.js有兩種模塊javascript
實現「模塊」功能的奧妙就在於JavaScript是一種函數式編程語言,它支持閉包。若是咱們把一段JavaScript代碼用一個函數包裝起來,這段代碼的全部「全局」變量就變成了函數內部的局部變量。java
var s = 'Hello'; var name = 'world'; console.log(s + ' ' + name + '!');
(function() { var s = 'Hello'; var name = 'world'; console.log(s + ' ' + name + '!'); })()
這樣一來,原來的全局變量s如今變成了匿名函數內部的局部變量。若是Node.js繼續加載其餘模塊,這些模塊中定義的「全局」變量s也互不干擾。node
因此,Node利用JavaScript的函數式編程的特性,垂手可得地實現了模塊的隔離。編程
Nodejs對加載過的模塊 會進行緩存,以減小二次引入時的開銷,引入模塊時會優先從緩存中查找,Node緩存的是編譯和執行以後的對象json
緩存形式: key-value的形式,以真實路徑做爲key,以編譯執行後的結果做爲value 放在緩存中(Module._cache對象中)(二次加載速度更快)
打印rquire.cache
能夠看到緩存的對象緩存
先說結論,因爲Node.js會緩存加載過的模塊,全部模塊的循環依賴並不會引發無限循環引用。舉個例子:閉包
a.js
文件下編程語言
console.log('a starting'); exports.done = false const b = require('./b.js') console.log('in a, b done = %j', b.done); exports.done = true console.log('a done');
b.js
文件下函數式編程
console.log('b starting'); exports.done = false // 這裏導入的是a未執行完的副本 const a = require('./a.js') console.log('in b, a done = %j', a.done); exports.done = true console.log('b done');
main.js
文件下函數
console.log('main starting'); const a = require('./a') const b = require('./b') console.log('in main.js, a done = %j, b done = %j', a.done, b.done);
整個詳細的過程分析以下:
因而可知,Node.js對已加載過的模塊進行緩存,解決了循環引用的問題,在二次加載時直接從緩存中取,提升了加載速度。
咱們須要瞭解,自定義模塊是動態加載的(運行時加載),在首次加載時,要通過路徑分析、文件定位、編譯執行的過程。
在分析路徑模塊時,require()
方法會去查找真實的路徑
package.json
中main
屬性指定的文件名進行定位 > index.js > index.node > index.json 依次匹配編譯和執行是引入文件模塊的最後一個階段。這裏只講對.js
文件的編譯,經過fs
模塊同步讀取文件後進行編譯,每一個編譯成功的模塊都會以它的真實路徑做爲索引緩存在Module._cache
對象上,以提升二次引入的性能。
編譯過程當中,Node會對獲取到的文件進行頭尾包裝
(function(module, exports, require, __filename, __dirname) { })
這樣每一個模塊之間都進行了做用域隔離 也解釋了咱們沒有在模塊文件中定義module、exports、__filename、 __dirname
這些變量卻能夠使用它們的緣由。
Node.js在執行一個javascript
文件時,會生成一個module
和exports
對象, module
還有一個exports
屬性,module.exports
和exports
指向同一個引用
二者的根本區別是:
exports返回的是模塊函數,module.exports返回的是模塊對象自己
舉個例子:a.js
文件下
let sayHello = function() { console.log('hello'); } exports.sayHello = sayHello
b.js
文件下
// 這樣使用會報錯 const sayHello = require('./a') sayHi() // 正確的方式 const func = require('./a') func.sayHello() // hello
新建c.js
文件
let sayHello = function() { console.log('hello'); } // 1方式導出 module.exports.sayHello = sayHello // 2方式導出 module.exports = sayHello
在b.js
中引入
// 1方式的 const func = require('./a') func.sayHello() // hello // 2方式的 const sayHello = require('./a') sayHello() // hello
能夠看出,1方式的導出跟exports的導出,在引入時的方式是一致的
module.exports.sayHello = sayHello 等同於 module.exports = { sayHello: sayHello } 也等同於 exports.sayHello = sayHello
還有一個注意的點是:執行require()
方法時,引入的是module.exports
導出的內容。
// d.js文件下: exports = { a: 200 } module.exports = { a: 100 } // b.js引入 const value = require('./d') console.log('value', value); // {a: 100}
從上面能夠看出,其實require導出的內容是module.exports的指向的內存塊內容,並非exports的。
// 若是d.js文件變成 exports = { a: 200 } // b.js引入 const value = require('./d') console.log('value', value); // {}
能夠看到打印出來的值爲{}
那是由於exports
原本指向跟module.exports
同一個引用,如今exports = {a: 200}
exports指向了另外一個內存地址,將與module.exports脫離關係,默認module.eports={}
總結