学习节点. js模块机制
一、CommonJS模块规范
Node与浏览器、W3C组织、CommonJS组织、ECMAScript的关系
Node已经参照CommonJS的Modules规范实现了一个模块系统,那么我们先来看看CommonJS的模块规范。
CommonJS对模块的定义非常简单,主要分为三个部分:模块引用、模块定义和模块标识。
1.模块参考
模块引用的示例代码如下:
var math=require(' math ');
在CommonJS规范中,有一个require()方法,它接受模块标识,将模块的API引入当前上下文。
2.模块定义
在模块中,上下文提供了一个require()方法来引入外部模块。与引入的函数相对应,上下文提供了exports对象用来导出当前模块的方法或变量,是唯一的导出出口。在模块中,还有一个模块对象,代表模块本身,导出是模块的一个属性。在节点中,文件是一个模块,可以通过将导出对象上的方法作为属性挂载来定义导出方法:
//math . jsexports . add=function(){ var sum=0,i=0,args=args . length;while(I l){ sum=args[I];}返回总和;};在另一个文件中,在我们通过require()方法引入模块后,我们可以调用已定义的属性或方法:
//program . jsvar math=require(' math ');exports . increment=function(val){ return math . add(val,1);};3.模块标识
模块标识符实际上是传递给require()方法的参数,它必须是以小驼峰命名的字符串,或者是以.或绝对路径。它可以没有文件名后缀. js,模块的定义很简单,界面也很简洁。其意义在于将聚类的方法和变量限制在私有范围内,并支持导入和导出功能来平滑连接上下游依赖关系。每个模块都有自己的空间,互不干扰,引用时也显得整洁。
二、节点的模块实现
在Node的实现中,并没有完全按照规范来实现,而是对模块规范做了一定的取舍,同时增加了一些自己需要的特性。虽然规范中的导出、需求和模块听起来非常简单,但是有必要知道Node在实现它们的过程中经历了什么。将模块引入节点需要以下三个步骤。
1.路径分析
2.文件定位
3.编译并执行
在Node中,模块分为两类:一类是Node提供的模块,称为核心模块;另一种是用户编写的模块,称为文件模块。
在Node源代码的编译过程中,核心模块被编译成二进制执行文件。Node进程启动时,一些核心模块直接加载到内存中,所以在引入这些核心模块时,可以省略文件定位和编译执行两个步骤,路径分析中优先考虑判断,因此其加载速度最快。
文件模块在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,比核心模块慢。
1.首先从缓存加载
就像前端浏览器缓存静态脚本文件以提高性能一样,Node缓存导入的模块以减少二次引入的开销。区别在于浏览器只缓存文件,而Node在编译和执行后缓存对象。无论是核心模块还是文件模块,require()方法对于同一个模块的二次加载总是采用缓存优先级,这是第一优先级。区别在于核心模块的缓存检查先于文件模块的缓存检查。
2.路径分析和文件位置
因为标识符有几种形式,不同的标识符模块的搜索和位置是不同的。
1).模块标识符分析节点根据模块标识符搜索模块。模块标识符在节点中主要分为以下几类。
核心模块,如http、fs、path等。或者.启动相对路径文件模块。以/开头的文件模块的绝对路径。非路径形式的文件模块,如自定义连接模块。
核心模块
核心模块的优先级仅次于缓存加载。在Node的源代码编译过程中已经编译成二进制代码,其加载过程最快。如果您试图加载与核心模块具有相同标识符的自定义模块,它将不会成功。如果您自己编写一个http用户模块,如果您想成功加载它,您必须选择不同的标识符或切换路径。
路径形式的文件模块
标识符以开头。和/在这里都被视为文件模块。分析路径模块时,require()方法会将路径变成实路径,并以实路径为索引,将编译后的结果存储在缓存中,使二次加载更快。由于文件模块向Node指示了确切的文件位置,因此可以在搜索过程中节省大量时间,并且其加载速度比核心模块慢。
自定义模块
自定义模块指的是非核心模块,不是路径形式的标识符。它是一个特殊的文件模块,可以是文件或包的形式。这种模块搜索是所有方法中最耗时和最慢的。
2).文件定位
缓存加载的优化策略使得二次导入时无需路径分析、文件定位、编译执行等过程,大大提高了模块的重载效率。但是在文件定位的过程中,仍然有一些细节需要注意,包括文件扩展名的分析、目录和包的处理。
文件扩展名分析
CommonJS模块规范还允许从标识符中排除文件扩展名。在这种情况下,Node将以的顺序补充扩展。js,json和。节点,并依次尝试它们。在尝试的过程中,需要调用fs模块来判断文件是否存在阻塞的情况。因为节点是单线程的,所以这是一个可能导致性能问题的地方。诀窍是:如果是的话。节点和。在json文件中,通过给传递给require()的标识符添加一个扩展名,速度会加快一点。
目录分析和打包
在分析标识符的过程中,分析文件扩展名后,require()可能找不到对应的文件,但会得到一个目录。此时,节点会将目录视为一个包。
在这个过程中,Node在一定程度上支持CommonJS包规范。首先Node在当前目录下搜索package . JSON(common js包规范定义的包描述文件),通过JSON.parse()解析包描述对象,取出主属性指定的文件名进行定位。如果文件名缺少扩展名,将进入扩展名分析步骤。而如果主属性指定的文件名错误,或者根本没有package.json文件,Node会以index作为默认文件名,然后依次搜索index.js、index.node和index.json。
如果在目录分析过程中没有成功找到文件,用户定义的模块将进入下一个模块路径进行搜索。如果遍历模块路径数组,仍然没有找到目标文件,将引发查找失败的异常。
3).模块在Node中编译,每个文件模块都是一个对象,定义如下:
函数模块(id,parent){ this . id=id;this . exports={ };this.parent=parentif(parent parent . children){ parent . children . push(this);} this.filename=nullthis.loaded=falsethis . children=[];}编译和执行是引入文件模块的最后阶段。定位特定文件后,Node会新建一个模块对象,然后根据路径加载编译。不同的文件扩展名有不同的加载方法,如下所示。js文件。
通过fs模块同步读取文件后,编译执行。节点文件。
这是一个用C/C编写的扩展文件,最终编译的文件是用dlopen()方法加载的。JSON文件。
通过fs模块同步读取文件后,使用JSON.parse()解析返回的结果。
其他扩展名文件。
它们都被加载为。js文件。
每个成功编译的模块都会将其文件路径缓存为模块上的索引。_缓存对象以提高二次导入的性能。
JavaScript模块的编译
回到CommonJS模块规范,我们知道每个模块文件中有三个变量:require、exports和module,但是它们在模块文件中没有定义,那么它们从何而来呢?甚至在Node的API文档中,我们知道每个模块中有两个变量__filename和__dirname,它们来自哪里?如果把定义模块的过程直接放在浏览器端,会污染全局变量。
事实上,在编译过程中,Node包装了JavaScript文件的内容。增加了(函数(导出,要求,模块,_ _ filename,_ _ dirname)\ \ n在头,\ n在尾);一个普通的JavaScript文件将被包装如下:
(函数(exports,require,module,__filename,_ _ dirname){ var math=require(' math ');exports.area=函数(半径){ return Math。PI *半径*半径;};});这样,每个模块文件的范围就被隔离了。包装好的代码将由vm原生模块的runInThisContext()方法执行(类似于eval,但是上下文清晰,没有全局污染),并返回一个具体的函数对象。最后,将当前模块对象的exports属性、require()方法、模块(模块对象本身)以及在文件位置获得的完整文件路径和文件目录作为参数传递给此函数()以供执行。
3.套餐和NPM
除了模块,package和NPM是连接模块的机制。
CommonJS的包规范的定义其实很简单。它由包结构和包描述文件组成。前者用于组织包中的各种文件,后者用于描述包的相关信息,供外部读取和分析。
1.程序包结构
包实际上是一个归档文件,也就是说,目录直接打包为中的一个文件。zip或tar.gz格式,安装后解压缩并还原到目录中。完全符合CommonJS规范的包目录应该包含以下文件。
Package.json:包描述文件。Bin:存储可执行二进制文件的目录。Lib:存储JavaScript代码的目录。Doc:存放文件的目录。测试:用于存储单元测试用例的代码。
2.包描述文件
包描述文件用于表达非代码相关信息。它是一个JSON格式的文件——package.json,位于包的根目录下,是包的重要组成部分。NPM的所有行为都与包描述文件领域密切相关。
你可以看到NPM官网的package.json的定义规范.
您可以通过npm adduser,npm publish将自己的包上传到npm仓库。
三、题外话:AMD,CMD,兼容各种模块规格的类库
1.超微半导体公司
是CommonJS模块规范的扩展,其模块定义如下:define(id?依赖性?工厂);
2.煤矿管理局
3.和睦相处
为了使同一个模块在前端和后端运行,需要考虑兼容前端在编写过程中也实现模块规范的环境。为了保持前后一致,类库开发人员需要将类库代码包装在一个闭包中。下面的代码演示了如何在不同的运行环境中定义hello()方法,该方法兼容Node、AMD、CMD和常见的浏览器环境:
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。
版权声明:学习节点. js模块机制是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。