内容简介:CommonJS是一个模块化的规范,首先我们先了解一下模块化加载:每个模块内部,都有一个
- CommonJS
- NodeJS模块化简单实现
- 附源码
- 总结
CommonJS
概述
CommonJS是一个模块化的规范, Nodejs 的模块系统,就是参照CommonJS规范实现的。每个文件就是一个模块 ,每个模块都要自己的作用域。
特点
- 所有代码都有运行在模块作用域,不会污染全局作用域
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
NodeJs 模块化简单实现
首先我们先了解一下模块化加载:
- 模块加载
- 模块解析文件名,解析出绝对路径来
- 我们引入时可以省略文件的后缀,有可能没有写后缀名, 会自动查找对应目录下
.js
或.json
的文件 - 多次调用只走一次, 得到一个真实存在的文件路径,在缓存中看文件是否存在,不存在则创建模块,然后放入到缓存中,方便别人读取
- 读取文件内容。如果是
.js
文件,就把内容加一个闭包 - 运行JS脚本,将结果返回
0. 创建module构造函数
每个模块内部,都有一个 module
对象,代表当前模块。
查看 console.log(module)
module都有哪些属性:
module.id module.filename module.loaded module.parent module.children module.exports
function Module(id) { // 下面就简单定义两个常用的 this.id = id this.exports = {} } 复制代码
1. 模块加载
引入自己写的模块
// 我们自己写的文件模块 要写路径 ./ 或者../ let hello = require('./1.NodeJS') console.log(hello) // first NodeJS 复制代码
引入内置模块就直接写名称即可
//操作文件的模块 let fs = require('fs') // 处理路径的模块 let path = require('path') // 虚拟机模块,沙箱运行,防止变量污染 let vm = require('vm') 复制代码
2. 解析文件,返回绝对路径
Module._resolveFilename
方法实现的功能是判断用户引入的模块是否包含后缀名,如果没有后缀名会根据 Module._extensions
的键的顺序查找文件,直到找到后缀名对应的文件的绝对路径,优先查找 .js
// 将引入文件处理为绝对路径 Module._resolveFilename = function (p) { // 以js或者json结尾的 if ((/\.js$|\.json$/).test(p)) { // __dirname当前文件所在的文件夹的绝对路径 // path.resolve方法就是帮我们解析出一个绝对路径出来 return path.resolve(__dirname, p); } else { // 没有后后缀 自动拼后缀 // Module._extensions 处理不同后缀的模块 let exts = Object.keys(Module._extensions); let realPath; // 存放真实存在文件的绝对路径 for (let i = 0; i < exts.length; i++) { // 依次匹配对应扩展名的绝对路径 let temp = path.resolve(__dirname, p + exts[i]) try { // 通过fs的accessSync方法对路径进行查找,找不到对应文件直接报错 fs.accessSync(temp) realPath = temp break } catch (e) { } } if (!realPath) { throw new Error('module not exists'); } // 将存在绝对路径返回 return realPath } } 复制代码
Module._extensions
处理对应模块扩展名。这里我们只提 .js
.json
,对应模块的处理功能我们在后面来实现
Module._extensions = { "js": function() {}, "json": function() {} } 复制代码
3. 多次调用只一次
当用户重复加载一个已经加载过的模块,我们只有第一次是加载,然后放入缓存中,后面在加载时,直接返回缓存中即可
Module._cacheModule = { }
存放模块缓存
// 获取内存中的结果 let cache = Module_cacheModule[filename] // 判断是否存在 if (cache) { // 如果存在直接将 exports对象返回 return cache.exports } // 如果不存在内存中,就创建模块,然后加入到内存中 let module = new Module(filename) Module._cacheModule[filename] = module 复制代码
4. 加载模块,对于不同类型的文件做不同的处理
根据前面我们说到的模块化,对于已经存放在内存中的我们直接返回就可以了,对于新添加的模块,我们该读取文件了, 根据传入的模块,尝试加载模块方法
// 根据传入的模块,尝试加载模块方法 function tryModuleLoad(module) { // 前面我们已经提到 module.id 为模块的识别符,通常是带有绝对路径的模块文件名 // path.extname 获取文件的扩展名 /* let ext = path.extname(module.id); // 如果扩展名是js 调用js处理器 如果是json 调用json处理器 Module._extensions[ext](module); // exports 上就有了数组 */ let ext = path.extname(module.id);//扩展名 // 如果扩展名是js 调用js处理器 如果是json 调用json处理器 Module._extensions[ext](module); // exports 上就有了数组 } 复制代码
Module._extensions
处理对应后缀名模块。这里我们只提 .js
.json
// 处理对应后缀名模块 Module._extensions = { ".js": function (module) { // 对于js文件,读取内容 let content = fs.readFileSync(module.id, 'utf8') // 给内容添加闭包, 后面实现 let funcStr = Module.wrap(content) // vm沙箱运行, node内置模块,前面我们已经引入, 将我们js函数执行,将this指向 module.exports vm.runInThisContext(funcStr).call(module.exports, module.exports, req, module) }, ".json": function (module) { // 对于json文件的处理就相对简单了,将读取出来的字符串转换未JSON对象就可以了 module.exports = JSON.parse(fs.readFileSync(module.id, 'utf8')) } } 复制代码
上面我们使用了 Module.wrap
方法,是帮助我们添加一个闭包,简单说就是我们在外面包了一个函数的前半段和后半段
// 存放闭包字符串 Module.wrapper = [ "(function (exports, require, module, __filename, __dirname) {", "})" ] 复制代码
// 将我们读到js的内容传入,组合成闭包字符串 Module.wrap = function (script) { return Module.wrapper[0] + script + Module.wrapper[1]; } 复制代码
5. 运行并返回结果
要运行,我们来看一下完整的模块加载代码
// 模块加载 Module._load = function (f) { // 相对路径,可能这个文件没有后缀,尝试加后缀 let fileName = Module._resolveFilename(f); // 获取到绝对路径 // 判断缓存中是否有该模块 if (Module._cacheModule[fileName]) { return Module._cacheModule[fileName].exports } let module = new Module(fileName); // 没有就创建模块 Module._cacheModule[fileName] = module // 并将创建的模块添加到缓存 // 加载模块 tryModuleLoad(module) return module.exports } 复制代码
到这里,一个简单的module就实现了,让我们测试一下吧
// 测试代码 function req(p) { return Module._load(p); // 加载模块 } // 第一次没有缓存,创建module,并添加到缓存中 let str = req('./1.NodeJS'); // 第二次就是返回的缓存中的 let str1 = req('./1.NodeJS.js'); console.log(str) // first NodeJS console.log(str1) // first NodeJS 复制代码
以上所述就是小编给大家介绍的《基于CommonJS规范,简单实现NodeJs模块化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Android模块化改造以及模块化通信框架
- Laravel 模块化开发模块 – Caffienate
- 前端模块化架构设计与实现(二|模块接口设计)
- 模块化编程的实践者 disconver 更新了用户模块
- ASP.NET Core模块化前后端分离快速开发框架介绍之4、模块化实现思路
- JavaScript模块化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。