🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 模块的分类 原生模块(内置模块、核心模块)、文件模块、第三方模块 - 原生模块:Node.js自带的模块 - 文件模块:我们自己写的模块 - 第三方模块:通过NPM下载的模块 ## 模块的加载机制 下图是个大概示例 ![](https://box.kancloud.cn/84a79f0cf70289b82694e5189e8c5b41_557x806.png) ## 文件的查找机制(非原生模块查找) 查找文件时,它会进行如下几种尝试(按照从上到下的优先级) ### 先当做一个文件路径名 - 尝试直接查找该文件, - 尝试添加扩展名后查找文件 ### 当做一个文件夹/包 路径名 - 尝试根据包查找文件 ->从包文描述文件获取文件名->尝试添加扩展名后查找文件 - 尝试查找该目录下的index(.js/.node) ### 顺着node_modules的链往上查找 如果到现在为止还没找到,且加载的不是相对路径,像这样`requrie('./a')`,那么会继续当做一个包并且顺着`module.paths`的路径依次往上查找node_modules里面有没有这么一个包/文件夹 like this ``` // [ 'd:\\WEB\\A\\node-basic\\03module\\node_modules', // 'd:\\WEB\\A\\node-basic\\node_modules', // 'd:\\WEB\\A\\node_modules', // 'd:\\WEB\\node_modules', // 'd:\\node_modules' ] ``` ## 模块加载的简单实现 **关键字:**`绝对路径与resolvePathname`、`后缀名与不同的加载方式`、`module.exports` ![](https://box.kancloud.cn/046e70874201a5c45ee95fec3302c9df_415x521.png) 首先我们定义一个我们自己的require方法和一个`Module`构造函数函数 ``` function Module(){} function req(filename){} ``` 这个构造函数下有一些属性 ``` Module._cache = {} //用来存放已经加载的模块 Module._extentions = ['.js','.json','.node']; //当文件没有后缀名时候会从这里取 ``` 其中所存放的`cache`是长这样的 ``` //cache { "文件的绝对路径":"Module的实例" ... } ``` 再让我们看看rq方法, 其中要做的第一件事是解析传入的文件名,是否是有效的,并且确保它是绝对路径。 为什么要确保它是绝对路径呢?有些相对路径在某些编辑器环境中代表的真正路径并不是我们想要的。我们使用绝对路径来读取文件会更可靠。 拿到解析后的路径后我们会尝试用这个路径作为key去`Module._cache`中获取缓存。 如有缓存是存在的我们就返回这个缓存模块下的`exports`,如果不存在我们就加载这个模块 ``` function req(){ filename = Module._resolvePathname(filename); let cacheModule = Module._cache['filename']; if(cacheModule){ return cacheModule.exports; } let module = new Module(filename) } ``` So,加载加载,是干了什么事呢? 我们所说的**加载**就是`Module`的实例化,并在这个实例上的`exports`上挂载我们`readFileSync`的那个文件内部`module.exports`所导出的内容。 但需要注意的是,加载不同的模块时我们做的事其实是有所区别的, ``` Module.prototype.load = function(){ let ext = path.extname(this.filename); Module._extentions[ext](this); } ``` 我们可以发现对于不同的文件类型我们将调用不同的方法去加载它。 - 如果是一个`json`文件,我们加载它就是直接将读取到的内容挂载在`module.exports`上。 ``` Module._extentions['json'] = function(module){ let json = fs.readFileSync(module.filename,'utf8'); json = JSON.parse(json); return module.exports = json; } ``` - 如果是一个`js`文件,我们加载它不仅要读取它的内容还要让这个js文件执行,并且是将它包起来作为一个闭包传入我们的module和req让它执行。 ``` Module._extentions['js'] = function(module){ let script = fs.readFileSync(module.filename,'utf8'); script = Module._wrap(script); vm.runInThisContext(script).call(module.exports,module.exports,req,module); } ``` **注意:** 在js的加载中,我们是将实例化后的module传入到闭包当中,在闭包中用module.exports导出模块的内容的。 --- 加载完成后,我们需要缓存模块。 而缓存就是将`module`实例存储到`Module._cahce`的过程,每一个`module`都有对应的一个`key`,这个key就是这个模块的绝对路径。 ``` function req(){ ... Module._cache[filename] = module; ... } ``` ![](https://box.kancloud.cn/01588f29b6007c5903a2c640bd055bb4_1250x406.png) ## 其它注意事项 - 模块的加载**是同步的!** ## 源码 仓库地址: [点我~](https://github.com/fancierpj0/module/blob/master/main.js)