1中引入模块的原理。模块机制1.1commonjs规范1.2node模块实现(node中引入模块的过程)1.2.1优先从缓存中加载1.2.2路径分析1.2.3文件位置1.2.4模块编译1.3核心模块1.3.1js核心模块编译过程1.3.2c/c++核心模块编译过程1.3.3核心模块引入过程1.4C/c++扩展模块1.模块机制1.1commonjs规范CommonJS规范是JavaScript有一个美好的愿景——让JavaScript无处不在。CommonJS模块的定义非常简单,主要分为模块引用、模块定义和模块标识三部分。模块引用:通过require方法导入模块varmath=require('math');模块定义:通过exports对象导出当前模块的方法或变量(exports是模块的属性)exports.add=function(){}模块标识:是传递给require方法的参数,可以是有以下几种形式(文件后缀名.js可以不要求):一个按照小驼峰命名的字符串,一个以.开头的相对路径,..一个绝对路径1.2node模块实现(在node的过程中introducingmodules)Node在实现上并没有完全遵循connonjs规范,而是针对模块规范做了一定的取舍,同时加入了一些自己的特性。1、node中模块的引入需要经过以下三个步骤:路径分析、文件定位、编译执行2、node中,模块分为以下两类:核心模块:node提供的模块核心模块在node运行时编译源码编译进入二进制可执行文件后,node进程启动时,会直接加载一些核心模块到内存中(所以引入这部分核心模块时,文件定位和编译执行两部分可以省略,并且在路径分析中优先判断,加载速度最快)文件模块:用户编写的模块,需要在运行时动态加载,需要完整的路径分析,文件定位,编译执行。速度较慢下面是详细的模块加载过程:1.2.1优先从缓存中加载节点。导入的模块会被缓存,减少二次导入的开销(与浏览器缓存的区别是:浏览器只缓存文件,而节点缓存编译执行的对象)核心模块的缓存检查优先于文件模块1.2.2路径分析由于标识符有多种形式,对于不同的标识符,模块搜索和定位有不同程度的差异。模块分类如下:核心模块:http、fs、path等以.开头的相对路径。核心模块的优先级仅次于缓存加载。在node源码编译过程中编译成二进制文件,加载速度最快。2.路径形式的文件模块require会将路径转换为真实路径,并以真实路径为索引,将编译执行结果放入缓存,二次加载更快。3、自定义模块节点会逐一尝试模块路径中的路径,直到找到目标文件。模块路径概念:模块路径是node在定位文件模块的具体文件时指定的搜索策略,具体表现为一个路径数组(可以通过module.paths输出)模块路径的生成规则:当前文件目录下的node_modules目录父目录下的node_modules目录父目录下的node_modules目录父目录下的node_modules目录逐级递归路径向上,直到根目录下的node_modules目录分析:当标识符通过对于require不包含文件扩展名,node会按照以下顺序完成扩展,依次尝试:.js.json.node在试用过程中,node会调用fs模块进行同步阻塞判断是否是文件存在。因为节点是单线程的,所以这是可能出现性能问题的地方。(解决办法:导入.json和.node文件时,加上扩展名)2.目录和包处理:require解析文件扩展名后可能会得到一个目录。这时候node会把目录当作一个包来处理。首先,node在当前目录中查找package.json。包描述对象通过json.parse解析,从中提取出main属性指定的文件名进行分析。如果扩展名缺失,则进入扩展名分析步骤。如果main指定的文件名错误,或者没有package.json,那么node会使用index作为文件名。然后依次搜索index.js、index.json、index.node。如果目录解析过程中没有成功定位到文件,自定义模块会进入下一个模块路径继续查找。如果遍历模块路径后仍然没有模块,则抛出查找失败的异常。1.2.4模块编译在定位到特定文件后,node会创建一个新的模块对象,然后根据路径加载编译。对于不同的扩展,加载方式如下:.js文件:通过fs模块同步读取文件后编译执行。node文件:这是一个用c/c++编写的扩展文件,最终编译后的文件是通过dlopen加载的。json文件:通过fs模块同步读取文件后,使用Json.parse解析并返回结果。其他扩展文件:全部加载为.js文件注意:每个编译成功的模块都会将其文件路径作为索引缓存在Module._cache对象中,以提高二次导入的性能1.js模块的编译节点包裹头部和获取的js文件的tail,wrapped代码会通过vm原生模块的runInThisContext()执行(function(exports,require,module,__filename,__dirname){...}2.c的编译节点/c++模块调用process.dlopen()加载执行,(dlope兼容windows和*nux平台上的libuv).node模块不需要编译,因为写好c/c++后编译生成module.3.json文件的编译通过fs模块同步读取,通过json.parse()获取json文件内容复制到module.exports1.3核心模块用c/c++编写(存放在node的src目录)js的编写(存放在lib目录)1.3.1js核心模块的编译过程转为c/c++代码:node使用v8附带的js2c.py工具,Convert将所有内置js代码放入c++数组中,生成node_natives.h头文件。(在这个过程中,js代码以字符串的形式存储在node命名空间中。node进程启动时,js代码直接加载到内存中)编译js核心模块:也经历了头尾打包的过程(与文件模块的区别在于:获取方式源代码和缓存执行结果的位置)从内存模块中加载编译成功的核心模块缓存在NativeModule._cache对象中;文件模块缓存在Module._cache对象中1.3.2c/c++核心模块编译过程等)c/c++完成核心部分,js实现封装1.内置模块定义好每个内置模块后,模块通过NODE_MODULE宏定义到节点命名空间中。nodex_extensions.h头文件将这些散列后的内置模块统一放入node_module_list数组node提供get_builtin_module()从node_module_list中提取内置模块导出内置模块:通过process.binding()加载内置模块(process是node启动时产生的全局变量)1.3.3核心模块引入流程NODE_MODULE(node_os,reg_func)get_builtin_module('node_os')process.binding('os')NativeModule.require('os')require('os')1.4c/c++扩展模块模块编写:普通扩展模块和内置模块的区别是:不需要将源码编译成node,而是通过dlopen()编译动态加载模块:通过gpy工具模块加载:通过require()加载.node文件,使用process.dlopen()加载通过uv_dlopen()打开动态链接库通过uv_dlsym()在动态链接库中找到NODE_MODULE宏定义的方法地址(注:以上两个方法都是在libuv库中实现的。不同操作系统下会调用不同的方法来加载操作系统下.node对应的文件.so和.dll)
