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={}
总结