前言模块是node不可或缺的一部分,是服务端编程的基础。在整理模块的同时,先对node部分模块的封装做一个总结。希望它真的能帮到你。本文将对CommenJS规范、node文件模块、核心模块进行全面梳理。如果喜欢我的文章,请点赞,欢迎Star~。欢迎关注我的github博文。我认为模块的出现是js进步最大的地方。因为有了模块,很多优秀的东西可以共享,不用担心变量污染和命名空间问题。node作为服务端javascript,借鉴了CommonJS规范,形成了一套简单易用的模块规范。首先看下面最常见的例子://circle.jsconst{PI}=Math;exports.area=x=>PI*x**2;exports.circle=x=>2*PI*x;//main.jsconstcircle=require('./circle');console.log(circle.area(4));//50.26548245743669其实我们可以清楚的看到,在这两个文件中,模块规范部分可以分为三部分:require(modulereference)=>这是整个模块体系的核心部分,可以引入其他模块并充分利用。exports(moduledefinition)=>另一个亮点是可以导出自己模块中的内容,供其他模块使用。Logo(modulelogo)=>其他人可以识别模块的东西。这三块内容可以用一张图来概括:如图所示:从这张图中可以看出,模块之间的接口可以通过exports暴露出来,然后通过require引入另一个模块中的内容。这样,我们就大致了解了模块的定义。主要分为三个部分:模块引用、模块定义和模块标识。但是,对于整个模块部分,我们最需要了解的是require机制。关于节点的require实现,有很多值得赞赏的地方。首先,您需要了解导入整个模块的步骤。从上面的例子中,我们可以看到这三个部分:路径分析=>如果我们拿一个例子来分析,'./circle'就是路径。(./类别是相对路径,当然也可以是绝对路径)文件位置=>通过分析的位置获取文件。编译执行=>只有编译后的文件才能放到其他模块中使用(后面我们也会分析如何编译)。有时,情况很特殊。模块本身分为核心模块和文档模块。编译节点源代码时编译核心模块。被编译成一个二进制文件。而一些核心模块会在node进程启动的时候直接加载到内存中,所以这部分核心模块的引入不需要经过文件定位和编译这些步骤。缓存中还有一个特殊的部分。每个模块第一次加载后,node会缓存其编译执行的对象,方便二次加载。因此,第二次加载时,优先缓存,从缓存中加载的模块不需要文件定位和编译。单从路径分析,可以分为三种不同的方式:核心模块,如http、fs、path等,加载优先级仅次于缓存加载,会直接编译成模块,形式为二进制文件路径,比如上面例子中的‘./circle’。路径清晰,搜索速度比较快,加载速度比核心模块慢。自定义模块多为npm包形式的文件,存放在node_modules中,没有对应的路径。这种搜索比较麻烦。查找方法:1.从当前目录下的node_modules中查看是否有对应的模块。2.如果有,直接加载使用。否则,它会搜索父目录下的node_modules目录,直到找到根目录下的node_modules。这种方式最慢。我们再分析一下文件位置:第一个例子中的标识符是'./circle'。可以发现这个文件标识符没有后缀。那么,node是怎么定位的呢?其实node有个默认的定位顺序:js,node,json。这里先识别js,再识别json和node文件。所以,这里有一个小技巧:在识别.node和.json文件时,带上文件扩展名会更快。为什么?是因为node使用了fs同步阻塞的方式,一一尝试,文件是否存在,有没有直接加载;如果不存在,则尝试下一个后缀名。以及那些自定义模块,例如npm包。节点的定位方法也不同。一般来说,npm包中都会有一个package.json文件。该文件中有一个main属性,指向整个包的入口文件;如果没有这个条件,node会加载index.js,index.json和index.node最后是模块编译部分的分析:首先,编译执行也可以通过上面三种后缀文件名来分析:jsfile=>fs模块同步读取后,编译执行nodefile=>.node该文件是c/c++文件模块的编译文件,使用dlopen方法加载引入文件。json文件=>json文件首先通过fs读取文件,然后通过JSON.parse方法编译执行。具有其他扩展名的文件将被视为js文件。同时,我们需要详细分析javascript文件编译的一些具体过程。编译完javascript文件,使用fs读取文件后,node会如何处理读取到的内容呢?会不会造成可变污染?很明显不是。根据CommonJS规范,node将读取的内容在头部和尾部进行包裹,包裹成function(exports,require,module,__dirname,__filename){...readcontent}。这样就起到了作用域隔离的作用,不会污染已有模块的内容。而__dirname、__filename存在于node中。然后使用vm原生模块的runInThisContext()方法执行这个函数代码(类似于eval=>将一个字符串转换成可执行的js代码)。然后它返回一个具体对象供现有模块中的内容使用。C/C++模块编译本次编译主要依赖node的process.dlopen()方法执行,node使用libuv对windows和*nix平台做兼容处理。该模块的性能高于普通文件模块,但写入成本会相应增加。json文件编译json文件编译会比较简单,就是通过fs读取文件,然后通过JSON.parse方法编译,最后将内容赋给已有模块中命名的变量。综上所述,我们大致梳理了node模块的整体机制,从模块导出,到导入,再到标识符解析。shadow可以从CommonJS中找到,但是node对它的处理比较完善。如果您对我写的内容有任何疑问,可以发表评论。如果我写的有错误,请指正。如果你喜欢我的博客,请关注我~哟。让我们一起总结,共同进步。欢迎关注我的github博客
