模块分类Node.js有两种模块核心模块一些核心模块已经直接加载到内存中,路径分析、编译和执行的步骤可以省略,先判断在路径分析,所以加载最快的文件模块是运行时动态加载的,所以需要完整的路径分析文件定位和编译执行过程,所以速度比核心模块慢实现“模块”功能的秘诀在于JavaScript是一种函数式编程语言,它支持闭包。如果我们用一个函数包裹一段JavaScript代码,那么这段代码的所有“全局”变量都变成了函数内部的局部变量。vars='你好';varname='世界';console.log(s+''+name+'!');(function(){vars='Hello';varname='world';console.log(s+''+name+'!');})()这样,原来的全局变量s现在是匿名函数内部的局部变量了。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量将不会相互干扰。因此,Node利用了JavaScript的函数式编程特性,轻松实现了模块的隔离。模块缓存机制Nodejs会对加载的模块进行缓存,减少二次导入的开销。导入模块时,会先从缓存中查找。Node缓存编译执行后的对象缓存形式:key-value的形式,以真实路径为key,编译执行后的结果为value,放入缓存中(在Module._cache对象中)(二次加载速度更快)打印rquire.cache,可以看到缓存对象模块的循环引用综上所述,由于Node.js缓存了加载的模块,所有模块的循环依赖不会造成无限循环引用。例如:console.log('astarting');exports.done=falseconstb=require('./b.js')console.log('ina,bdone=%j'ina.jsfile,b.完成);exports.done=trueconsole.log('完成');b.js文件下的console.log('bstarting');exports.done=false//这里的import是a没有被执行过consta=require('./a.js')console.log('inb,adone=%j',a.done);exports.done=trueconsole.log('bdone');mainconsole.log('mainstarting');consta=require('./a')constb=require('./b')console.log('inmain.js,adone=%in.jsfilej,bdone=%j',a.done,b.done);整个详细流程分析如下:nodemain.js需要a.js,加载a.js,输出“astarting”a:exports.done=false,需要b.js,加载b.js输出“bstarting”,b:exports.done=falserequirea.js,因为a.js还没有执行完,导出未执行完的副本,所以a={done:false}在b中输出,a.done=falseb:exports.done=true,输出bdone,b.js执行完毕,返回a.js继续执行b={done:true},输出a,b.done=true,输出adonea.js执行完成,a={done:true},返回main.js继续执行,需要b.js,因为b.js已经执行完毕,获取缓存中的值,现在a={done:true},b={done:true}输出总的来说,a.done=true,b.done=true可以看出Node.js对加载的模块进行了缓存,解决了循环引用的问题,二次加载时直接从缓存中取,提高了加载速度,路径分析和文件位置我们需要了解自定义模块是动态加载的(在运行时加载)。第一次加载时,要经过路径解析、文件定位、编译执行等过程。在分析路径模块时,require()方法会搜索真正的路径。如果没有扩展名,则解析顺序为:.js>.node>.json。如果没有找到对应的文件,而是一个目录,则将其视为一个包,首先查找package.json中main属性指定的文件名location>index.js>index.node>index.json匹配模块的编译、编译和执行是导入文件模块a阶段的最后一步。这里只讲.js文件的编译。fs模块同步读取文件,然后编译它。每个编译成功的模块都会缓存在Module._cache对象上,并以其真实路径为索引,以提高二次导入的性能。.在编译过程中,Node会将获取到的文件从头到尾包装起来(function(module,exports,require,__filename,__dirname){}),这样每个模块在作用域上都是隔离的,也说明我们不在module定义了文件中可以使用module、exports、__filename、__dirname等变量的原因。module.exports和exports的区别Node.js在执行一个javascript文件时,会生成一个module和exports对象,module也有一个exports属性,module.exports和exports指向同一个引用两者的根本区别是:exports返回的是模块函数,module.exports返回的是模块对象本身。例如:让sayHello=function(){console.log('hello');}exports.sayHello=sayHellob.jsunderfilea.js//这样使用会报错)func.sayHello()//hello创建一个新的c.js文件letsayHello=function(){console.log('hello');}//1种导出方式module.exports.sayHello=sayHello//2种方式导出module.exports=sayHelloIntroduce//1wayconstfuncintob.js=require('./a')func.sayHello()//hello//constsayHelloinmode2=require('./a')sayHello()//hello可以看出方式1的导出和exports导出一样,导入的方式是一致的}也相当于exports.sayHello=sayHello还有一点需要注意:在执行require()方法的时候,引入的是module.exports导出的内容。//d.js文件下:exports={a:200}module.exports={a:100}//b.js引入constvalue=require('./d')console.log('value',价值);//{a:100}从上面可以看出require导出的内容其实是module.exports指向的内存块的内容,而不是exports。//如果d.js文件变为exports={a:200}//b.js引入constvalue=require('./d')console.log('value',value);//{}可以看出打印的值为{},因为exports本来指向和module.exports同一个引用,现在exports={a:200}exports指向另一个内存地址,会和module.exports分开.默认情况下,module.eports={}总结exports是对module.exports的引用module.exports初始化是一个{},exports也是这个{}需要引用返回module.exports,不是exportsexports.xxx=xxxx相当于加上直接给export对象的属性或者修改属性值,可以直接在调用模块中看到exports=xxx重新分配内存给exports,会和module.exports分开,两者没有关联。调用模块将不可访问。参考:Node模块机制不是完整的指南
