VM

通过 require('vm') 可以加载该模块。利用该模块可以立即编译执行或先编译保存后执行 JavaScript 代码。

Class: Script

该类封装了一些预编译脚本,并在沙盒中运行这些脚本。

new vm.Script(code, options)

该方法是一个构造函数,创建一个 Script 实例并编译 code,但不运行编译后的代码,最终返回的 vm.Script 对象存储了编译后的代码。使用下面的函数可以反复运行 vm.Script 中的代码。vm.Script 中的代码并没有绑定到任何的全局对象上,它们会在运行前绑定,并运行后解绑。

参数 options 包含以下属性:

  • filename,该属性允许开发者更改堆栈跟踪信息中的文件名
  • lineOffset,该属性用于在堆栈跟踪信息中为行号设置偏移位置
  • columnOffset,该属性用于在堆栈跟踪信息中为列号设置偏移位置
  • displayErrors,该属性决定在抛出异常之前,是否向 stderr 输出错误并高亮问题代码。只有编译期的语法问题会被抛出,执行期的问题则有 Script 的函数处理
  • timeout,用于指定 code 执行的超时时间,超时则中断执行,并抛出错误
  • cacheData,该属性是一个包含 V8 代码缓存数据的 Buffer。cachedDataRejected 是布尔类型,其值取决于是否接受 V8 的数据。
  • produceCachedDate,如果值为 true 且没有 cachedData 参数,V8 会尝试为 code 缓存代码,如果缓存成功,就会生成一个 Buffer 实例存储代码的缓存数据,该 Buffer 实例也会成为返回的 vm.Script 的一个实例。cachedDataProduced 是一个布尔类型,其值取决于系统是否成功生成了代码缓存数据。

script.renInContext(contextifiedSandbox[, options])

该方法与 vm.runInContext() 类似,但后者是挂载在预编译 Script 对象下的一个方法。scirpt.runInContext() 方法在 contextifiedSandbox 中运行 Script 编译后的代码并返回相应的结果。运行代码时并不会访问本地作用域。

script.runInContext 接收的 optionsscript.runInThisContext() 一致。

下面代码演示了使用 VM 模块编译后的代码修改全局变量并反复执行的效果:

  1. const util = require('util');
  2. const vm = require('vm');
  3. var sandbox = {
  4. animal: 'cat',
  5. count: 2
  6. };
  7. var context = new vm.createContext(sandbox);
  8. var script = new vm.Script('count += 1; name = "kitty"');
  9. for (var i = 0; i < 10; ++i) {
  10. script.runInContext(context);
  11. }
  12. console.log(util.inspect(sandbox));
  13. // { animal: 'cat', count: 12, name: 'kitty' }

注意运行不可信代码时需要万分小心。script.runInContext() 虽然很有用,但是运行不可信代码时最好使用一个独立的进程。

script.runInNewContext([sandbox][, options])

该方法与 vm.runInNewContext() 类似,但后者是挂载在预编译 Script 对象下的一个方法。scirpt.runInNewContext() 如果没有收到 sandbox 就会自建一个沙盒,然后在沙盒中以全局对象的形式运行 Script 编译后的代码。运行代码时并不会访问本地作用域。

script.runInNewContext 接收的 optionsscript.runInThisContext() 一致。

下面代码演示了使用 VM 模块编译后的代码修改全局变量并反复执行的效果:

  1. const util = require('util');
  2. const vm = require('vm');
  3. const sandboxes = [{}, {}, {}];
  4. const script = new vm.Script('globalVar = "set"');
  5. sandboxes.forEach((sandbox) => {
  6. script.runInNewContext(sandbox);
  7. });
  8. console.log(util.inspect(sandboxes));
  9. // [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]

注意运行不可信代码时需要万分小心。script.runInNewContext() 虽然很有用,但是运行不可信代码时最好使用一个独立的进程。

script.runInThisContext([options])

该方法与 vm.runInThisContext() 类似,但后者是挂载在预编译 Script 对象下的一个方法。scirpt.runInThisContext() 方法运行 Script 编译后的代码并返回相应的结果。运行代码时没有访问本地作用域的权限,但有访问 global 对象的权限。

下面代码演示了使用 script.runInThisContext() 编译代码并多次运行的效果:

  1. const vm = require('vm');
  2. global.globalVar = 0;
  3. const script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });
  4. for (var i = 0; i < 1000; ++i) {
  5. script.runInThisContext();
  6. }
  7. console.log(globalVar);
  8. // 1000

options 参数接收一下参数:

  • filename,该属性允许开发者更改堆栈跟踪信息中的文件名
  • lineOffset,该属性用于在堆栈跟踪信息中为行号设置偏移位置
  • columnOffset,该属性用于在堆栈跟踪信息中为列号设置偏移位置
  • displayErrors,该属性决定在抛出异常之前,是否向 stderr 输出错误并高亮问题代码。只有编译期的语法问题会被抛出,执行期的问题则有 Script 的函数处理
  • timeout,用于指定 code 执行的超时时间,超时则中断执行,并抛出错误

vm.createContext([sandbox])

如果指定了 sandbox 对象,那么该使用该对象调用 vm.runInContext()script.runInContext() 方法。在脚本内部,sandbox 会被视为全局对象,包含所有的已有属性以及标准全局对象所拥有的内建对象和方法。在 VM 模块所执行的脚本外部,sandbox 不会被修改。

如果指定沙盒对象,那么该方法返回一个新的空沙盒对象。

该方法常用于创建一个可以运行多个脚本的沙盒,比如为一个模拟浏览器创建煞和对象,该沙盒对象包含了全局对象 window 的属性和方法,最后将所有的 script 标签置于沙盒中运行。

vm.isContext(sandbox)

该方法返回一个布尔值,用于判定沙盒对象是否经由 vm.createContext() 初始化过。

vm.runInContext(code, contextifiedSandbox[, options])

vm.runInContext() 方法先编译 code,然后使用指定的 contextifiedSandbox 运行代码并返回结果。运行中的代码没有访问本地作用域的权限。contextifiedSandbox 对象必须使用 vm.createContext() 创建或初始化,它会被视为 code 的全局对象。

vm.runInContext 接收的 optionsvm.runInThisContext() 一致。

下面代码演示了使用同一个上下文环境编译和执行不同脚本的效果:

  1. const util = require('util');
  2. const vm = require('vm');
  3. const sandbox = { globalVar: 1 };
  4. vm.createContext(sandbox);
  5. for (var i = 0; i < 10; ++i) {
  6. vm.runInContext('globalVar *= 2;', sandbox);
  7. }
  8. console.log(util.inspect(sandbox));
  9. // { globalVar: 1024 }

注意运行不可信代码时需要万分小心。vm.runInContext() 虽然很有用,但是运行不可信代码时最好使用一个独立的进程。

vm.runInDebugContext(code)

vm.runInDebugContext() 在 V8 的调试上下文环境中编译和执行 code。该方法最主要的场景是获取 V8 的调试对象:

  1. const Debug = vm.runInDebugContext('Debug');
  2. Debug.scripts().forEach(function(script) { console.log(script.name); });

注意,这里的调试上下文和对象都是和 V8 的调试器绑定在一起的,所以当 V8 引擎有所改动时可能并不会有警告。

通过 --expose_debug_as= switch 也可以暴漏调试对象。

vm.runInNewContext(code[, sandbox][, options])

vm.runInNewContext() 如果没有收到 sandbox 就会自建一个沙盒,然后在沙盒中编译 code,最后使用沙盒作为全局对象执行编译后的代码并返回结果。

vm.runInNewContext 接收的 optionsvm.runInThisContext() 一致。

下面代码演示了使用 VM 模块编译后的代码修改全局变量并反复执行的效果:

  1. const util = require('util');
  2. const vm = require('vm');
  3. const sandbox = {
  4. animal: 'cat',
  5. count: 2
  6. };
  7. vm.runInNewContext('count += 1; name = "kitty"', sandbox);
  8. console.log(util.inspect(sandbox));
  9. // { animal: 'cat', count: 3, name: 'kitty' }

注意运行不可信代码时需要万分小心。vm.runInNewContext() 虽然很有用,但是运行不可信代码时最好使用一个独立的进程。

vm.runInThisContext(code[, options])

vm.runInThisContext() 方法用于编译执行 code 并返回结果。运行代码时没有访问本地作用域的权限,但有访问 global 对象的权限。

下面代码演示了使用 vm.runInThisContext()eval() 运行相同代码的差别:

  1. const vm = require('vm');
  2. var localVar = 'initial value';
  3. const vmResult = vm.runInThisContext('localVar = "vm";');
  4. console.log('vmResult: ', vmResult);
  5. console.log('localVar: ', localVar);
  6. const evalResult = eval('localVar = "eval";');
  7. console.log('evalResult: ', evalResult);
  8. console.log('localVar: ', localVar);
  9. // vmResult: 'vm', localVar: 'initial value'
  10. // evalResult: 'eval', localVar: 'eval'

vm.runInThisContext() 并没有访问本地作用域的权限,所以 localVar 不会被修改。eval 具有访问本地作用域的权限,所以可以修改 loalVar

虽然 vm.runInThisContext() 的这种用法很类似直接调用 eval(),比如 (0,eval)('code'),但是 vm.runInThisContext() 的优势在于还可以接收以下参数:

  • filename,该属性允许开发者更改堆栈跟踪信息中的文件名
  • lineOffset,该属性用于在堆栈跟踪信息中为行号设置偏移位置
  • columnOffset,该属性用于在堆栈跟踪信息中为列号设置偏移位置
  • displayErrors,该属性决定在抛出异常之前,是否向 stderr 输出错误并高亮问题代码。只有编译期的语法问题会被抛出,执行期的问题则有 Script 的函数处理
  • timeout,用于指定 code 执行的超时时间,超时则中断执行,并抛出错误