【NodeJS】浅析加载模块的机制

本文介绍了NodeJS的程序入口,重点解析了VM模块,包括VM的三个常用方法以及如何在不同环境中执行JavaScript代码。同时,文章详细阐述了NodeJS的模块加载与缓存机制,解释了模块执行的过程和上下文。最后,提到了正确导出模块的方法,为读者理解NodeJS模块系统提供了深入的见解。

1、程序入口

先写一个非常简单的例子来认识 Node 的总入口

// file hello.js
(function(){
    console.log('Hello Node.js')
})()

在命令行下执行 node hello.js 即可看到输出 Hello Node.js

2、VM模块

  在 Node.js 核心模块中,有一个用于执行JavaScript代码的 VM虚拟机模块。该模块与JavaScript全局函数eval()类似,提供了一个JavaScript代码执行的沙箱环境。通过VM,JavaScript代码可以被编译后立即执行,也可以编译后保存稍后执行。

// file hello.js
(function(){
    console.log('Hello NodeJS~')
})  // 注意这里与前一个例子区别是 没有最后一个()
// file main.js
const vm = require('vm');
const fs = require('fs');
const path = require('path');

var prt = path.resolve(__dirname, '.', 'hello.js');
function stripBOM(content){
    if(content.charCodeAt(0) === 0xFEFF){
        content = content.slice(1);
    }
    return content;
}

var wrapper = stripBOM(fs.readFileSync(prt, 'utf8'));
var compiledWrapper = vm.runInThisContext(wrapper, {
    filename: prt,
    lineOffset: 0,
    displayErrors: true
});

compiledWrapper();

此时运行 node main.js 即可看到输出 Hello NodeJs~

下面对可能遇到的问题总结一下:

1)关于 .charCodeAt(0) === 0xFEFF

  • 0xFEFF —— 零宽度非换行的空格 (每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格”,用FEFF表示。这正好是两个字节,而且FF比FE大1)
  • 0x200B —— 零宽度空格
  • 0x200C —— 零宽度非连字空格
  • 0x200D —— 零宽度连字空格

2)fs.readFileSync(filename, [encoding])
3)VM模块包含了三个常用的方法:

  • vm.runInThisContext(code[, options]) :创建一个独立的沙箱环境,以执行对参数code的编译,运行并返回结果。没有权限访问本地作用域,但是可以访问Global全局对象。

    • filename: 指定这个脚本产生的堆栈跟踪信息所使用的文件名
    • lineOffset:指定这个脚本产生的堆栈跟踪信息的行号偏移量
    • columnOffset:指定这个脚本产生的堆栈跟踪信息的列号偏移量
    • displayErrors: 当被设置为真的时候,如果在编译代码时发生了错误,造成错误的行号会被附加到堆栈跟踪信息中。默认为 true。
    • timeout: 指定中断执行前代码执行的最长时间,如果执行被中断,就会抛出一个错误
    • breakOnSigint: 如果为真,当接收到(Ctrl+C)时,执行会被中断。在脚本执行期间,连接到process.on(“SIGINT”)上存在的事件处理程序将会被禁用,但是在这之后脚本会继续工作。如果执行被中断了,就会抛出一个错误。
  • vm.runInContext(code, contextifiedSandbox[, options]) :若要使执行的代码不可访问Global全局对象可以使用vm.runInContext()方法执行代码,执行前需要用vm.createContext([sandbox])方法创建一个沙箱环境。

  • vm.runInNewContext(code[, sandbox][, options]) :如果提供了沙箱环境,则将将沙箱环境上下文化并使用,否则创建一个新的沙箱环境,将沙盒作为全局变量运行代码并返回执行结果。

3、模块加载与缓存

用Node.js编写程序,一个js文件对应一个模块,在module.js文件中,其构造函数定义如下

function Module(id, parent) {
    this.id = id;
    this.exports = {};
    this.parent = parent;
    if (parent && parent.children) {
        parent.children.push(this);
    }
    this.filename = null;
    this.loaded = false;
    this.children = [];
}

node加载一个js文件时,会先new 一个 Module

var module = new Module(filename, parent);

然后读入Js代码并对文件进行头尾包装。如此这般之后,Js文件里面的代码变成这个匿名函数内部的语句。

(function (exports, require, module, __filename, __dirname) {
    //原始文件内容
});

  上述形式的代码实际上是一个函数字面量,说的直白点,就是一个定义匿名函数的表达式。Node使用V8编译并运行上面的代码,也就是对以上表达式求值,其值是一个函数对象。V8将结果返回给Node。

//右侧表达式的值是一个函数对象
var fn = (function(){
}) ;

  Node得到返回的函数对象,使用apply方法,指定上下文,传入 module.exports,require方法, module,文件名以及路径作为参数,执行函数。在这一步,开始执行Js文件内部的代码。

var args = [this.exports, require, this, filename, dirname];
var result = compiledWrapper.apply(this.exports, args);  //compiledWrapper 为函数对象

  由源代码可知,Js文件运行的上下文环境是module.exports,因此在文件中,也可以直接使用this导出对象。一旦一个文件加载之后,其对应的模块被缓存,其他文件又require的时候,直接取缓存。

var cachedModule = Module._cache[filename];
if (cachedModule) {
    return cachedModule.exports;
}

4、正确导出模块

我已在之前的 【NodeJS】浅析 exports 与 module.exports 的区别 及 export default 与 export 的区别 分析的很详细

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值