在node.js中,模块使用CommonJS规范,一个文件是一个模块javascript
node.js中的模块可分为三类java
node.js提供了大量的模块供咱们使用,好比 想解析一个文件的路径,可使用path模块下的相应方法实现:node
const path = require('path');
//返回目标文件的绝对路径
console.log(path.resolve('./1.txt'));
复制代码
运行结果:npm
/Users/cuiyue/workspace/test/1.txt
复制代码
使用require引入相应的模块,便可使用。json
node.js的每一个模块都有这两个参数,它们都是一个绝对路径的地址,区别是__filename存放了从根目录到当前文件名的路径,__dirname只存放从根目录到模块的所在目录:缓存
console.log(__dirname);
console.log(__filename);
复制代码
运行结果:安全
/Users/cuiyue/workspace/test
/Users/cuiyue/workspace/test/module.js
复制代码
vm模块是node.js提供在V8虚拟机中编译和运行的工具,node.js中的模块内部实现就是经过此模块完成。闭包
说说vm的基本用法。app
在js环境中有一个eval函数,它能够运行js的代码字符串,好比:函数
eval('console.log("Hello javascript.")'); //输出Hello javascript.
复制代码
能够看到,eval函数的参数是一段字符串,它能够运行字符串形式的js代码,但它可使用上下文环境中的变量:
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建立了指定的上下文环境中,拿不到外界的参量。
在之前,因为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方法的参数,必须是符合小驼峰命名的字符串,或者以 .、..、开头的相对路径,或者绝对路径。
以上为大体流程,下面尝试着写一下模块。
代码的基本结构:
/** * 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方法的主要做用是把传入的模块解析成绝对路径,这样才能够进行下一步,根据完整的路径加载模块。
所以要进行判断,若是传入的模块不存在,则要报错;若是传入的模块已经有扩展名了,就不要拼接了;若没有扩展名,依次以.js .json .node的顺序拼接成完成的模块进行加载。
此对象中封装了加载不一样类型模块的处理方法,其中如果.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就更可避免一些误解,写出精细的代码。