在node.js中,模块使用CommonJS规范,一个文件是一个模块
node.js中的模块可分为三类
node.js提供了大量的模块供我们使用,比如 想解析一个文件的路径,可以使用path模块下的相应方法实现:
const path = require('path'); //返回目标文件的绝对路径 console.log(path.resolve('./1.txt'));
运行结果:
/Users/cuiyue/workspace/test/1.txt
使用require引入相应的模块,即可使用。
__dirname和__filename
node.js的每个模块都有这两个参数,它们都是一个绝对路径的地址,区别是__filename存放了从根目录到当前文件名的路径,__dirname只存放从根目录到模块的所在目录:
console.log(__dirname); console.log(__filename);
运行结果:
/Users/cuiyue/workspace/test
/Users/cuiyue/workspace/test/module.js
vm模块
vm模块是node.js提供在V8虚拟机中编译和运行的工具,node.js中的模块内部实现就是通过此模块完成。
说说vm的基本用法。
在js环境中有一个eval函数,它可以运行js的代码字符串,比如:
eval('console.log("Hello javascript.")'); //输出Hello javascript.
可以看到,eval函数的参数是一段字符串,它可以运行字符串形式的js代码,但它可以使用上下文环境中的html" target="_blank">变量:
var num=100; eval('console.log(num)'); //输出100
以上是可以正确访问num的值。
vm模块提供了方法创建一个安全的沙箱,在指定的上下文环境中运行代码,不受外界干扰。
const vm = require('vm'); var num = 100; vm.runInThisContext('console.log(num)');
运行结果:
console.log(num)
^
ReferenceError: num is not defined
可以看到代码报错了,说明在vm创建了指定的上下文环境中,拿不到外界的参量。
CommonJS规范
在以前,由于javascript的历史原因导致它的模块机制很差,由于这些缺点使得javascript不太善于开发大型应用,于是提出了CommonJS规范以弥补javascript的不足。
CommonJS规范主要分为三块内容:模块导入导出、模块定义、模块标识。
模块导入导出
CommonJS中使用require()函数进行模块的引入。
const mymodule = require('mymodule');
使用exports导出模块
module.exports = { name: 'Tom' };
引用的名称可以不带路径,若不带路径表示引入的是node提供的模块或是npm安装的第三方模块(node_modules)
模块定义
module对象:在每一个模块中,module对象代表该模块自身。
export属性:module对象的一个属性,它向外提供接口。
模块标识
模块标识指的是传递给require方法的参数,必须是符合小驼峰命名的字符串,或者以 .、..、开头的相对路径,或者绝对路径。
node中模块解析流程
以上为大致流程,下面尝试着写一下模块。
代码的基本结构:
/** * Module类,用于处理模块加载 */ function Module() {} //模块的缓存 Module._cacheModule = {}; //不同扩展名的加载策略 Module._extensions = {}; //根据moduleId解析绝对路径, Module._resolveFileName = function(moduleId) {}; //入口函数 function req(moduleId) {}
附上全部代码:
const path = require('path'); const fs = require('fs'); const vm = require('vm'); /** * Module类,用于处理模块加载 */ function Module(file) { this.id = file; //当前模块的id,它使用完整的绝对路径标识,因此是唯一的 this.exports = {}; //导出 this.loaded = false; //模块是否已加载完毕 } //模块的缓存 Module._cacheModule = {}; Module._wrapper = ['(function(exports,require,module,__dirname,__filename){', '});']; //不同扩展名的加载策略 Module._extensions = { '.js': function(currentModule) { let js = fs.readFileSync(currentModule.id, 'utf8'); //读取出js文件内容 let fn = Module._wrapper[0] + js + Module._wrapper[1]; vm.runInThisContext(fn).call( currentModule.exports, currentModule.exports, req, currentModule, path.dirname(currentModule.id), currentModule.id); return currentModule.exports; }, '.json': function(currentModule) { let json = fs.readFileSync(currentModule.id, 'utf8'); return JSON.parse(json); //转换为JSON对象返回 }, '.node': '' }; //加载模块(实例方法) Module.prototype.load = function(file) { let extname = path.extname(file); //获取后缀名 return Module._extensions[extname](this); }; //根据moduleId解析绝对路径, Module._resolveFileName = function(moduleId) { let p = path.resolve(moduleId); if (!path.extname(moduleId)) { //传入的模块没有后缀 let arr = Object.keys(Module._extensions); //循环读取不同扩展名的文件 for (var i = 0; i < arr.length; i++) { let file = p + arr[i]; //拼接上后缀名成为一个完整的路径 try { fs.accessSync(file); return file; //若此文件存在返回它 } catch (e) { console.log(e); } } } else { return p; } }; function req(moduleId) { let file = Module._resolveFileName(moduleId); if (Module._cacheModule[file]) { //若缓存中存在此模块 return Module._cacheModule[file]; } else { let module = new Module(file); module.exports = module.load(file); return module.exports; } } console.log(req('./a.js')());
a.js的文件内容:
module.exports = function() { console.log('This message from a.js'); console.log(__dirname); console.log(__filename); }
最终运行结果:
This message from a.js
/Users/cuiyue/workspace/test
/Users/cuiyue/workspace/test/a.js
重要代码说明
_resolveFileName
_resolveFileName方法的主要作用是把传入的模块解析成绝对路径,这样才可以进行下一步,根据完整的路径加载模块。
因此要进行判断,如果传入的模块不存在,则要报错;如果传入的模块已经有扩展名了,就不要拼接了;若没有扩展名,依次以.js .json .node的顺序拼接成完成的模块进行加载。
_extensions
此对象中封装了加载不同类型模块的处理方法,其中若是.json类型则使用fs读取文件直接转换成JSON对象并返回。
若是.js文件则读取后,拼接闭包,将exports,require,module,__dirname,__filename五大参数拼接好,使用vm模块的沙箱机制运行,得到的结果放入module.exports返回。
总结
以上就是node.js的模块加载的简单逻辑,实际上node.js的源码远远比上面的代码复杂,光是处理模块路径、判断合法等操作就写了N行。而且我这里没有写缓存以及其它的复杂逻辑,但核心差不多就是这些,核心的核心就是用fs.readFileSync读取js文件,把内容拼接到一个大大的闭包中,这也解释了为什么我们自己写的所有node模块中都会有require方法,exports导出,以及__dirname和__filename参数。
了解了node.js的模块加载逻辑,在以后写node.js就更可避免一些误解,写出精细的代码。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍seaJs的模块定义和模块加载浅析,包括了seaJs的模块定义和模块加载浅析的使用技巧和注意事项,需要的朋友参考一下 SeaJS 是由玉伯开发的一个遵循 CommonJS 规范的模块加载框架,可用来轻松愉悦地加载任意 JavaScript 模块和css模块样式。SeaJS非常小巧,小巧在于压缩和gzip后体积只有4K,而且接口和方法也非常少,SeaJS 就两个核心:模块定义和 模块的
本文向大家介绍Node.js模块加载详解,包括了Node.js模块加载详解的使用技巧和注意事项,需要的朋友参考一下 JavaScript是世界上使用频率最高的编程语言之一,它是Web世界的通用语言,被所有浏览器所使用。JavaScript的诞生要追溯到Netscape那个时代,它的核心内容被仓促的开发出来,用以对抗Microsoft,参与当时白热化的浏览器大战。由于过早的发布,无可避免的造成了它的
问题内容: 是否可以异步加载Node.js模块? 这是标准代码: 但是我想写这样的东西: 有没有办法做到这一点?还是有一个很好的理由为什么不支持回调? 问题答案: 尽管是同步的,并且Node.js并未提供现成的异步变体,但是您可以轻松地自己构建一个变体。 首先,您需要创建一个模块。在我的示例中,我将编写一个模块,该模块从文件系统异步加载数据,但是当然是YMMV。因此,首先,使用老式的,不需要的同步
本文向大家介绍浅谈Webpack 是如何加载模块的,包括了浅谈Webpack 是如何加载模块的的使用技巧和注意事项,需要的朋友参考一下 Webpack 在前端开发中作为模块打包工具非常受开发者的青睐,丰富的 loader 使它可以实现各种各样的功能。本文将通过 webpack 来打包一个 js 文件,看看 webpack 是如何加载各个模块的。 两个简单的源文件 为了方便分析 webpack 加载
基本用法 管理前端模块 生成前端模块 脚本文件的实时生成 browserify-middleware模块 参考链接 随着JavaScript程序逐渐模块化,在ECMAScript 6推出官方的模块处理方案之前,有两种方案在实践中广泛采用:一种是AMD模块规范,针对模块的异步加载,主要用于浏览器端;另一种是CommonJS规范,针对模块的同步加载,主要用于服务器端,即node.js环境。 Brows
本文向大家介绍浅析Python requests 模块,包括了浅析Python requests 模块的使用技巧和注意事项,需要的朋友参考一下 Python requests 模块 requests 模块是我们使用的 python爬虫 模块 可以完成市场进80%的爬虫需求。 安装 使用 requests模块代码编写的流程: - 指定url - 发起请求 - 获取响应对象中的数据 - 持久化存储