我是靠谱客的博主 烂漫小猫咪,这篇文章主要介绍深入了解Node.js中的VM模块,现在分享给大家,希望可以做个参考。

VM模块是NodeJS里面的核心模块,支撑了require方法和NodeJS的运行机制,我们有些时候可能也要用到VM模板来做一些特殊的事情。本篇文章就来带大家详细了解下Node中的VM模块,希望对大家有所帮助!

在上一篇文章中,我们提到了一个问题。

字符串如何能变成 JS 执行呢?

我们详细介绍了两种方法,分别是 eval函数 和 new Function 。

在这里我们需要再强调一下, 由 Function 构造器创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量,不能访问它们被 Function 构造器创建时所在的作用域的变量。这一点与使用 eval 执行创建函数的代码不同。

复制代码
1
2
3
4
global.a = 100; // 挂在到全局对象global上 var b = 200; // this !== global new Function("console.log(a)")() // 100 new Function("console.log(b)")() // b is not defined
登录后复制

Function 可以获取全局变量,所以他还是可能会有变量污染的情况出现。Function模块引擎的实现原理 ,后续我会出一篇文章进行单独讲解。

还有一种解决方案,我们在上一次文章中没有进行详细的展开,那就是 vm模块

vm模块

在上述文字中,我一直在强调一个概念,那就是 变量的污染

VM的特点就是不受环境的影响,也可以说他就是一个 沙箱环境 (沙箱模式给模块提供一个环境运行而不影响其它模块和它们私有的沙箱)

复制代码
1
2
3
4
5
6
const vm = require('vm') global.a = 100; // 运行在当前环境中[当前作用域] vm.runInThisContext('console.log(a)'); // 100 // 运行在新的环境中[其他作用域] vm.runInNewContext('console.log(a)'); // a is not defined
登录后复制

在这里我们要强调一下,因为 在Node.js中全局变量是在多个模块下共享的,所以尽量不要在global中定义属性。 Demo中的定义是为了方便理解。

假设我们在同级目录下有一个文件 1.js ,里面定义了 global.a = 100;。 现在我们引入这个文件

复制代码
1
2
requrie(./1); console.log(a); // 100
登录后复制

我们可以发现,在当前文件中我们并没有定义变量a,仅仅只是把两个模块文件关联在了一起。这就是我上面提到的,Node中全局变量是在多个模块下共享的。

他的原理是因为在 Node 的环境中,全局中有一个执行上下文。

复制代码
1
2
3
4
5
6
// 模拟一下Node的全局环境 // vm.runInThisContext在当前全局环境执行,但不会产生新函数 - function(exports, module, require, __dirname, __filename){ // ... } - vm.runInThisContext ... // vm.runInNewContext在全局环境之外执行 vm.runInNewContext ...
登录后复制

所以,vm.runInThisContext 可以访问到 global上的全局变量,但是访问不到自定义的变量。而 vm.runInNewContext 访问不到 global,也访问不到自定义变量,他存在于一个全新的执行上下文。

而我们require 就是通过 vm.runInThisContext 实现的。

实现require 主要可以分为以下四步。

  • 读取需要引入的文件。

  • 读取到文件后,将代码封装成一个函数。

  • 通过 vm.runInThisContext 将他转变成 JS 语法。

  • 代码调用。

假设我们现在有以下两个文件。分别是 a.jsb.js

复制代码
1
2
3
4
5
6
// 文件a通过module.exports导出一个变量,在文件b中使用require进行接收。 // a.js module.exports = "a" // b.js let a = require('./a'); console.log(a); // a
登录后复制

我们可以通过上面的四个步骤,分析一下导入导出的实现逻辑是什么样的。

  • 读取文件。

    将需要引入的文件内容引入到需要接收的文件里,就会变成这个样子

    复制代码
    1
    let a = module.exports = "a";
    登录后复制

    但是这种形式,Node根本解析不了,所以我们就需要进行第二步。

  • 将读取的文件封装成函数。

    复制代码
    1
    2
    3
    4
    let a = (function(exports, module, require, __dirname, __filename){ module.exports = "a"; return module.exports })(...args) // exports, module, require, __dirname, __filename 将五个参数传入
    登录后复制

    封装成函数的原因,我们可以参考下面这个例子。

    假设我们现在传入的不是字符串,而是一个函数。

    复制代码
    1
    2
    3
    // a.js var a = 100; module.exports = function(){}
    登录后复制

    这样我们在解析的时候,就会被解析成下面这种格式

    复制代码
    1
    2
    3
    4
    5
    let a = (function(exports, module, require, __dirname, __filename){ var a = 100; module.exports = function(){}; return module.exports })(...args) // exports, module, require, __dirname, __filename 将五个参数传入
    登录后复制

    我们导出的是 module.exports,所以在模块文件中定义的变量a,也只属于当前这个执行上下文。

    在解析的时候,变量a 会被放到函数中。真正的实现了 作用域分离

  • vm.runInThisContext 解析成可执行的Js代码

    我们处理过的代码会以字符串的形式存在,所以我们需要通过vm.runInThisContext将字符串进行解析。

  • 进行代码调用

    在此之前,我们其实还需要对代码进行调试。

最后

以上就是烂漫小猫咪最近收集整理的关于深入了解Node.js中的VM模块的全部内容,更多相关深入了解Node.js中内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(101)

评论列表共有 0 条评论

立即
投稿
返回
顶部