- assert断言
- async_hooks异步钩子
- async_hooks/context异步上下文
- buffer缓冲区
- C++插件
- C/C++插件(使用Node-API)
- C++嵌入器
- child_process子进程
- cluster集群
- CLI命令行
- console控制台
- Corepack核心包
- crypto加密
- crypto/webcrypto网络加密
- debugger调试器
- deprecation弃用
- dgram数据报
- diagnostics_channel诊断通道
- dns域名服务器
- domain域
- Error错误
- events事件触发器
- fs文件系统
- global全局变量
- http超文本传输协议
- http2超文本传输协议2.0
- https安全超文本传输协议
- inspector检查器
- Intl国际化
- module模块
- module/cjsCommonJS模块
- module/esmECMAScript模块
- module/package包模块
- net网络
- os操作系统
- path路径
- perf_hooks性能钩子
- permission权限
- policy安全策略
- process进程
- punycode域名代码
- querystring查询字符串
- readline逐行读取
- repl交互式解释器
- report诊断报告
- stream流
- stream/web网络流
- string_decoder字符串解码器
- test测试
- timers定时器
- tls安全传输层
- trace_events跟踪事件
- tty终端
- url网址
- util实用工具
- v8引擎
- vm虚拟机
- wasi网络汇编系统接口
- worker_threads工作线程
- zlib压缩
Node.js v16.18.1 文档
- Node.js 16.18.1
-
►
目录
- vm 虚拟机
vm.Script
类vm.Module
类vm.SourceTextModule
类vm.SyntheticModule
类vm.compileFunction(code[, params[, options]])
vm.createContext([contextObject[, options]])
vm.isContext(object)
vm.measureMemory([options])
vm.runInContext(code, contextifiedObject[, options])
vm.runInNewContext(code[, contextObject[, options]])
vm.runInThisContext(code[, options])
- 示例:在 VM 中运行 HTTP Server
- 上下文隔离化一个对象意味着什么?
- 与异步任务和 Promise 的超时交互
- vm 虚拟机
-
►
索引
- assert 断言
- async_hooks 异步钩子
- async_hooks/context 异步上下文
- buffer 缓冲区
- C++插件
- C/C++插件(使用Node-API)
- C++嵌入器
- child_process 子进程
- cluster 集群
- CLI 命令行
- console 控制台
- Corepack 核心包
- crypto 加密
- crypto/webcrypto 网络加密
- debugger 调试器
- deprecation 弃用
- dgram 数据报
- diagnostics_channel 诊断通道
- dns 域名服务器
- domain 域
- Error 错误
- events 事件触发器
- fs 文件系统
- global 全局变量
- http 超文本传输协议
- http2 超文本传输协议2.0
- https 安全超文本传输协议
- inspector 检查器
- Intl 国际化
- module 模块
- module/cjs CommonJS模块
- module/esm ECMAScript模块
- module/package 包模块
- net 网络
- os 操作系统
- path 路径
- perf_hooks 性能钩子
- permission 权限
- policy 安全策略
- process 进程
- punycode 域名代码
- querystring 查询字符串
- readline 逐行读取
- repl 交互式解释器
- report 诊断报告
- stream 流
- stream/web 网络流
- string_decoder 字符串解码器
- test 测试
- timers 定时器
- tls 安全传输层
- trace_events 跟踪事件
- tty 终端
- url 网址
- util 实用工具
- v8 引擎
- vm 虚拟机
- wasi 网络汇编系统接口
- worker_threads 工作线程
- zlib 压缩
- ► 其他版本
- 文档搜索
目录
- vm 虚拟机
vm.Script
类vm.Module
类vm.SourceTextModule
类vm.SyntheticModule
类vm.compileFunction(code[, params[, options]])
vm.createContext([contextObject[, options]])
vm.isContext(object)
vm.measureMemory([options])
vm.runInContext(code, contextifiedObject[, options])
vm.runInNewContext(code[, contextObject[, options]])
vm.runInThisContext(code[, options])
- 示例:在 VM 中运行 HTTP Server
- 上下文隔离化一个对象意味着什么?
- 与异步任务和 Promise 的超时交互
vm 虚拟机#
源代码: lib/vm.js
node:vm
模块允许在 V8 虚拟机上下文中编译和运行代码。
node:vm
模块不是安全的机制。
不要使用它来运行不受信任的代码。
JavaScript 代码可以立即编译并运行,也可以编译、保存并稍后运行。
常见的用例是在不同的 V8 上下文中运行代码。 这意味着被调用的代码与调用代码具有不同的全局对象。
可以通过上下文隔离化一个对象来提供上下文。 调用的代码将上下文中的任何属性视为全局变量。 由调用的代码引起的对全局变量的任何更改都反映在上下文对象中。
const vm = require('node:vm');
const x = 1;
const context = { x: 2 };
vm.createContext(context); // 上下文隔离化对象。
const code = 'x += 40; var y = 17;';
// `x` 和 `y` 是上下文中的全局变量。
// 最初,x 的值为 2,因为这是 context.x 的值。
vm.runInContext(code, context);
console.log(context.x); // 42
console.log(context.y); // 17
console.log(x); // 1; y 未定义。
vm.Script
类#
vm.Script
类的实例包含可以在特定上下文中执行的预编译脚本。
new vm.Script(code[, options])
#
code
<string> 要编译的 JavaScript 代码。options
<Object> | <string>filename
<string> 指定此脚本生成的堆栈跟踪中使用的文件名。 默认值:'evalmachine.<anonymous>'
。lineOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的行号偏移量。 默认值:0
。columnOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的第一行列号偏移量。 默认值:0
。cachedData
<Buffer> | <TypedArray> | <DataView> 为所提供的源提供可选的Buffer
或TypedArray
或DataView
,其中包含 V8 的代码缓存数据。 当提供时,cachedDataRejected
值将设置为true
或false
,具体取决于 V8 对数据的接受程度。produceCachedData
<boolean> 当true
且没有cachedData
存在时,则 V8 将尝试为code
生成代码缓存数据。 当成功后,会生成带有 V8 代码缓存数据的Buffer
并存储在返回的vm.Script
实例的cachedData
属性中。cachedDataProduced
值将设置为true
或false
,这取决于代码缓存数据是否成功生成。 此选项已弃用,支持script.createCachedData()
。 默认值:false
。importModuleDynamically
<Function> 在调用import()
时在评估此模块期间调用。 如果未指定此选项,则调用import()
将使用ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
拒绝。 此选项是实验模块 API 的一部分。 不建议在生产环境中使用它。specifier
<string> 传给import()
的说明符script
<vm.Script>importAssertions
<Object> 传给optionsExpression
可选参数的"assert"
值,如果没有提供值,则为空对象。- 返回: <Module Namespace Object> | <vm.Module> 建议返回
vm.Module
以利用错误跟踪,并避免包含then
函数导出的命名空间出现问题。
如果 options
是字符串,则指定文件名。
创建新的 vm.Script
对象编译 code
但不运行它。
编译后的 vm.Script
可以多次运行。
code
没有绑定到任何全局对象;相反,它在每次运行之前绑定,只是为了那次运行。
script.cachedDataRejected
#
当提供 cachedData
来创建 vm.Script
时,该值将根据 V8 对数据的接受情况设置为 true
或 false
。
否则值为 undefined
。
script.createCachedData()
#
- 返回: <Buffer>
创建可与 Script
构造函数的 cachedData
选项一起使用的代码缓存。
返回 Buffer
。
此方法可以随时调用任意次数。
Script
的代码缓存不包含任何 JavaScript 可观察状态。
代码缓存可以安全地与脚本源一起保存,并用于多次构造新的 Script
实例。
Script
源代码中的函数可以标记为延迟编译,并且在构建 Script
时不会编译它们。
这些函数将在第一次调用时被编译。
代码缓存序列化 V8 目前知道的关于 Script
的元数据,它可以用来加速未来的编译。
const script = new vm.Script(`
function add(a, b) {
return a + b;
}
const x = add(1, 2);
`);
const cacheWithoutAdd = script.createCachedData();
// In `cacheWithoutAdd` the function `add()` is marked for full compilation
// upon invocation.
script.runInThisContext();
const cacheWithAdd = script.createCachedData();
// `cacheWithAdd` 包含完全编译的函数 `add()`。
script.runInContext(contextifiedObject[, options])
#
contextifiedObject
<Object>vm.createContext()
方法返回的上下文隔离化的对象。options
<Object>- 返回: <any> 脚本中执行的最后一条语句的结果。
在给定的 contextifiedObject
中运行 vm.Script
对象包含的编译代码并返回结果。
运行代码无权访问本地作用域。
下面的示例编译代码,增加一个全局变量,设置另一个全局变量的值,然后多次执行代码。
全局变量包含在 context
对象中。
const vm = require('node:vm');
const context = {
animal: 'cat',
count: 2
};
const script = new vm.Script('count += 1; name = "kitty";');
vm.createContext(context);
for (let i = 0; i < 10; ++i) {
script.runInContext(context);
}
console.log(context);
// 打印: { animal: 'cat', count: 12, name: 'kitty' }
使用 timeout
或 breakOnSigint
选项将导致新的事件循环和相应的线程被启动,其性能开销非零。
script.runInNewContext([contextObject[, options]])
#
contextObject
<Object> 将被上下文隔离化的对象。 如果为undefined
,则将创建新的对象。options
<Object>displayErrors
<boolean> 当为true
时,如果编译code
时出现Error
,则导致错误的代码行会附加到堆栈跟踪中。 默认值:true
。timeout
<integer> 指定终止执行前执行code
的毫秒数。 如果执行终止,则将抛出Error
。 此值必须是严格的正整数。breakOnSigint
<boolean> 如果为true
,则接收SIGINT
(Ctrl+C)将终止执行并抛出Error
。 已通过process.on('SIGINT')
附加的事件的现有句柄在脚本执行期间被禁用,但在此之后继续工作。 默认值:false
。contextName
<string> 新创建的上下文的可读名称。 默认值:'VM Context i'
, 其中i
是创建的上下文的升序数字索引。contextOrigin
<string> 对应于新创建的用于显示目的的上下文的来源。 来源的格式应该像 URL,但只有协议、主机和端口(如果需要),就像URL
对象的url.origin
属性的值。 最值得注意的是,该字符串应省略尾部斜杠,因为它表示路径。 默认值:''
。contextCodeGeneration
<Object>microtaskMode
<string> 如果设置为afterEvaluate
,则微任务(通过Promise
和async function
调度的任务)将在脚本运行后立即运行。 在这种情况下,它们包含在timeout
和breakOnSigint
范围内。
- 返回: <any> 脚本中执行的最后一条语句的结果。
首先对给定的 contextObject
进行上下文隔离化,在创建的上下文中运行 vm.Script
对象包含的编译代码,并返回结果。
运行代码无权访问本地作用域。
以下示例编译设置全局变量的代码,然后在不同的上下文中多次执行代码。
全局变量设置并包含在每个单独的 context
中。
const vm = require('node:vm');
const script = new vm.Script('globalVar = "set"');
const contexts = [{}, {}, {}];
contexts.forEach((context) => {
script.runInNewContext(context);
});
console.log(contexts);
// 打印: [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]
script.runInThisContext([options])
#
在当前 global
对象的上下文中运行 vm.Script
包含的编译代码。
运行代码无权访问局部作用域,但可以访问当前 global
对象。
下面的示例编译了增加 global
变量的代码,然后多次执行该代码:
const vm = require('node:vm');
global.globalVar = 0;
const script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });
for (let i = 0; i < 1000; ++i) {
script.runInThisContext();
}
console.log(globalVar);
// 1000
vm.Module
类#
此特性仅在启用 --experimental-vm-modules
命令标志时可用。
vm.Module
类为在 VM 上下文中使用 ECMAScript 模块提供了低层接口。
它是 vm.Script
类的对应物,它密切反映了 ECMAScript 规范中定义的模块记录。
但是,与 vm.Script
不同,每个 vm.Module
对象都从它的创建开始绑定到上下文。
与 vm.Script
对象的同步性质相比,对 vm.Module
对象的操作本质上是异步的。
'async' 函数的使用有助于操作 vm.Module
对象。
使用 vm.Module
对象需要三个不同的步骤:创建/解析、链接、以及评估。
以下示例说明了这三个步骤
此实现位于比 ECMAScript 模块加载器更低的级别。 虽然计划提供支持,但也无法与加载器交互。
import vm from 'node:vm';
const contextifiedObject = vm.createContext({
secret: 42,
print: console.log,
});
// 步骤 1
//
// 通过构造新的 `vm.SourceTextModule` 对象来创建模块。
// 这会解析提供的源文本,如果出现任何问题,则抛出 `SyntaxError`。
// 默认情况下,在顶层上下文中创建了模块。
// 但在这里,我们指定 `contextifiedObject` 作为该模块所属的上下文。
//
// 这里,我们尝试从模块 "foo" 中获取默认导出,
// 并将其放入本地绑定的 "secret" 中。
const bar = new vm.SourceTextModule(`
import s from 'foo';
s;
print(s);
`, { context: contextifiedObject });
// 步骤 2
//
// "Link" 此模块的导入依赖项。
//
// 提供的链接回调(“链接器”)接受两个参数:
// 父模块(在本例中为 `bar`)和作为导入模块的说明符的字符串。
// 回调应该返回
// 与提供的说明符相对应的模块,
// 在`module.link()` 中记录了某些要求。
//
// 如果返回的模块尚未开始链接,
// 则将在返回的模块上调用相同的链接器回调。
//
// 即使是没有依赖关系的顶级模块也必须明确链接。
// 然而,提供的回调永远不会被调用。
//
// link() 方法返回 Promise,
// 当链接器返回的所有 Promise 都解决时,则该 Promise 将被解决。
//
// 注意:这是一个人为的示例,
// 每次调用链接器函数时都会创建新的 "foo"模块。
// 在成熟的模块系统中,可能会使用缓存来避免重复的模块。
async function linker(specifier, referencingModule) {
if (specifier === 'foo') {
return new vm.SourceTextModule(`
// "secret" 变量指的是我们在创建上下文时
// 添加到 "contextifiedObject" 中的全局变量。
export default secret;
`, { context: referencingModule.context });
// 在这里使用 `contextifiedObject`
// 而不是 `referencingModule.context` 也可以。
}
throw new Error(`Unable to resolve dependency: ${specifier}`);
}
await bar.link(linker);
// 步骤 3
//
// 评估模块。
// evaluate() 方法返回 promise,其将在模块完成评估后解决。
// 打印 42。
await bar.evaluate();
const vm = require('node:vm');
const contextifiedObject = vm.createContext({
secret: 42,
print: console.log,
});
(async () => {
// 步骤 1
//
// Create a Module by constructing a new `vm.SourceTextModule` object. This
// parses the provided source text, throwing a `SyntaxError` if anything goes
// wrong. By default, a Module is created in the top context. But here, we
// specify `contextifiedObject` as the context this Module belongs to.
//
// Here, we attempt to obtain the default export from the module "foo", and
// put it into local binding "secret".
const bar = new vm.SourceTextModule(`
import s from 'foo';
s;
print(s);
`, { context: contextifiedObject });
// 步骤 2
//
// "Link" 此模块的导入依赖项。
//
// 提供的链接回调(“链接器”)接受两个参数:the
// parent module (`bar` in this case) and the string that is the specifier of
// the imported module. The callback is expected to return a Module that
// corresponds to the provided specifier, with certain requirements documented
// in `module.link()`.
//
// If linking has not started for the returned Module, the same linker
// callback will be called on the returned Module.
//
// Even top-level Modules without dependencies must be explicitly linked. The
// callback provided would never be called, however.
//
// link() 方法返回 Promise,
// 当链接器返回的所有 Promise 都解决时,则该 Promise 将被解决。
//
// Note: This is a contrived example in that the linker function creates a new
// "foo" module every time it is called. In a full-fledged module system, a
// cache would probably be used to avoid duplicated modules.
async function linker(specifier, referencingModule) {
if (specifier === 'foo') {
return new vm.SourceTextModule(`
// The "secret" variable refers to the global variable we added to
// "contextifiedObject" when creating the context.
export default secret;
`, { context: referencingModule.context });
// Using `contextifiedObject` instead of `referencingModule.context`
// here would work as well.
}
throw new Error(`Unable to resolve dependency: ${specifier}`);
}
await bar.link(linker);
// 步骤 3
//
// Evaluate the Module. The evaluate() method returns a promise which will
// resolve after the module has finished evaluating.
// 打印 42。
await bar.evaluate();
})();
module.dependencySpecifiers
#
该模块所有依赖项的说明符。 返回的数组被冻结,不允许对其进行任何更改。
对应于 ECMAScript 规范中循环模块记录的 [[RequestedModules]]
字段。
module.error
#
如果 module.status
为 'errored'
,则该属性包含模块在求值过程中抛出的异常。
如果状态是别的,访问这个属性会导致抛出异常。
值 undefined
不能用于由于可能与 throw undefined;
有歧义而没有抛出异常的情况。
对应于 ECMAScript 规范中循环模块记录的 [[EvaluationError]]
字段。
module.evaluate([options])
#
评估模块。
必须在模块链接后调用,否则会拒绝。
当模块已经被评估时,它也可以被调用,在这种情况下,如果初始评估成功结束(module.status
是 'evaluated'
),则它将不做任何事情,或者它会重新抛出初始评估导致的异常(module.status
是 'errored'
)。
在评估模块时无法调用此方法(module.status
为 'evaluating'
)。
对应 ECMAScript 规范中循环模块记录的 Evaluate() 具体方法字段。
module.identifier
#
当前模块的标识符,在构造函数中设置。
module.link(linker)
#
linker
<Function>-
specifier
<string> 请求模块的说明符:import foo from 'foo'; // ^^^^^ 模块说明符
-
referencingModule
<vm.Module>Module
对象link()
被调用。 -
extra
<Object>assert
<Object> 来自断言的数据:
根据 ECMA-262,主机应该忽略他们不支持的断言,而不是,例如,如果存在不受支持的断言则触发错误。import foo from 'foo' assert { name: 'value' }; // ^^^^^^^^^^^^^^^^^ 断言
-
返回: <vm.Module> | <Promise>
-
- 返回: <Promise>
链接模块依赖项。 此方法必须在求值前调用,并且每个模块只能调用一次。
该函数应返回 Module
对象或最终解析为 Module
对象的 Promise
。
返回的 Module
必须满足以下两个不变量:
- 它必须与父
Module
属于相同的上下文。 - 它的
status
不能是'errored'
。
如果返回的 Module
的 status
是 'unlinked'
,则将在返回的 Module
上递归调用此方法,并使用相同提供的 linker
函数。
link()
返回 Promise
,当所有链接实例都解析为有效的 Module
时,它将被解析,或者如果链接器函数抛出异常或返回无效的 Module
,则被拒绝。
链接器函数大致对应于 ECMAScript 规范中实现定义的 HostResolveImportedModule 抽象操作,有几个关键区别:
- 当 HostResolveImportedModule 是同步的时,允许链接器函数是异步的。
在模块链接期间使用的实际 HostResolveImportedModule 实现是一种返回链接期间链接的模块的实现。 因为那时所有模块都已经完全链接了,HostResolveImportedModule 实现是完全同步的每个规范。
对应 ECMAScript 规范中循环模块记录的 Link() 具体方法字段。
module.namespace
#
模块的命名空间对象。
这仅在链接 (module.link()
) 完成后可用。
对应于 ECMAScript 规范中的 GetModuleNamespace 抽象操作。
module.status
#
模块的当前状态。 将是以下之一:
-
'unlinked'
:module.link()
还没有被调用。 -
'linking'
:module.link()
已被调用,但链接器函数返回的 Promise 尚未全部解决。 -
'linked'
: 模块已成功链接,其所有依赖都已链接,但尚未调用module.evaluate()
。 -
'evaluating'
: 该模块正在通过自身或父模块上的module.evaluate()
进行评估。 -
'evaluated'
: 模块已成功评估。 -
'errored'
: 模块已被评估,但抛出异常。
除了 'errored'
,此状态字符串对应于规范的循环模块记录的 [[Status]]
字段。
'errored'
对应于规范中的 'evaluated'
,但 [[EvaluationError]]
设置为不是 undefined
的值。
vm.SourceTextModule
类#
此特性仅在启用 --experimental-vm-modules
命令标志时可用。
- 继承自: <vm.Module>
vm.SourceTextModule
类提供了 ECMAScript 规范中定义的源文本模块记录。
new vm.SourceTextModule(code[, options])
#
code
<string> 要解析的 JavaScript 模块代码options
identifier
<string> 用于堆栈跟踪的字符串。 默认值:'vm:module(i)'
其中i
是上下文特定的升序索引。cachedData
<Buffer> | <TypedArray> | <DataView> 为所提供的源提供可选的Buffer
或TypedArray
或DataView
,其中包含 V8 的代码缓存数据。code
必须与创建此cachedData
的模块相同。context
<Object>vm.createContext()
方法返回的上下文隔离化的对象,用于编译和评估此Module
。lineOffset
<integer> 指定在此Module
产生的堆栈跟踪中显示的行号偏移量。 默认值:0
。columnOffset
<integer> 指定在此Module
生成的堆栈跟踪中显示的第一行列号偏移量。 默认值:0
。initializeImportMeta
<Function> 在评估此Module
期间调用以初始化import.meta
。meta
<import.meta>module
<vm.SourceTextModule>
importModuleDynamically
<Function> 在调用import()
时在评估此模块期间调用。 如果未指定此选项,则调用import()
将使用ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
拒绝。specifier
<string> 传给import()
的说明符module
<vm.Module>importAssertions
<Object> 传给optionsExpression
可选参数的"assert"
值,如果没有提供值,则为空对象。- 返回: <Module Namespace Object> | <vm.Module> 建议返回
vm.Module
以利用错误跟踪,并避免包含then
函数导出的命名空间出现问题。
创建新的 SourceTextModule
实例。
分配给作为对象的 import.meta
对象的属性可能允许模块访问指定 context
之外的信息。
使用 vm.runInContext()
在特定上下文中创建对象。
import vm from 'node:vm';
const contextifiedObject = vm.createContext({ secret: 42 });
const module = new vm.SourceTextModule(
'Object.getPrototypeOf(import.meta.prop).secret = secret;',
{
initializeImportMeta(meta) {
// 注意:这个对象是在顶层上下文中创建的。因此,
// Object.getPrototypeOf(import.meta.prop) 指向
// 顶层上下文中的 Object.prototype,
// 而不是在上下文对象中。
meta.prop = {};
}
});
// 由于模块没有依赖关系,链接器函数永远不会被调用。
await module.link(() => {});
await module.evaluate();
// 现在,Object.prototype.secret 将等于 42。
//
// 要解决这个问题,则将上面的
// meta.prop = {};
// 替换为
// meta.prop = vm.runInContext('{}', contextifiedObject);
const vm = require('node:vm');
const contextifiedObject = vm.createContext({ secret: 42 });
(async () => {
const module = new vm.SourceTextModule(
'Object.getPrototypeOf(import.meta.prop).secret = secret;',
{
initializeImportMeta(meta) {
// 注意:这个对象是在顶层上下文中创建的。因此,
// Object.getPrototypeOf(import.meta.prop) 指向
// 顶层上下文中的 Object.prototype,
// 而不是在上下文对象中。
meta.prop = {};
}
});
// 由于模块没有依赖关系,链接器函数永远不会被调用。
await module.link(() => {});
await module.evaluate();
// 现在,Object.prototype.secret 将等于 42。
//
// 要解决这个问题,则将上面的
// meta.prop = {};
// 替换为
// meta.prop = vm.runInContext('{}', contextifiedObject);
})();
sourceTextModule.createCachedData()
#
- 返回: <Buffer>
创建可与 SourceTextModule
构造函数的 cachedData
选项一起使用的代码缓存。
返回 Buffer
。
在评估模块之前,可以多次调用此方法。
SourceTextModule
的代码缓存不包含任何 JavaScript 可观察状态。
代码缓存可以安全地与脚本源一起保存,并用于多次构造新的 SourceTextModule
实例。
SourceTextModule
源代码中的函数可以标记为延迟编译,并且在构建 SourceTextModule
时不会编译它们。
这些函数将在第一次调用时被编译。
代码缓存序列化 V8 目前知道的关于 SourceTextModule
的元数据,它可以用来加速未来的编译。
// 创建初始模块
const module = new vm.SourceTextModule('const a = 1;');
// 从这个模块创建缓存数据
const cachedData = module.createCachedData();
// 使用缓存数据创建新的模块。代码必须相同。
const module2 = new vm.SourceTextModule('const a = 1;', { cachedData });
vm.SyntheticModule
类#
此特性仅在启用 --experimental-vm-modules
命令标志时可用。
- 继承自: <vm.Module>
vm.SyntheticModule
类提供了 WebIDL 规范中定义的合成模块记录。
合成模块的目的是提供通用的接口,用于将非 JavaScript 源暴露给 ECMAScript 模块图。
const vm = require('node:vm');
const source = '{ "a": 1 }';
const module = new vm.SyntheticModule(['default'], function() {
const obj = JSON.parse(source);
this.setExport('default', obj);
});
// 在链接中使用 `module`...
new vm.SyntheticModule(exportNames, evaluateCallback[, options])
#
exportNames
<string[]> 将从模块导出的名称数组。evaluateCallback
<Function> 在评估模块时调用。options
创建新的 SyntheticModule
实例。
分配给此实例导出的对象可能允许模块的导入者访问指定 context
之外的信息。
使用 vm.runInContext()
在特定上下文中创建对象。
syntheticModule.setExport(name, value)
#
此方法用于模块链接后设置导出的值。
如果在链接模块之前调用,则会抛出 ERR_VM_MODULE_STATUS
错误。
import vm from 'node:vm';
const m = new vm.SyntheticModule(['x'], () => {
m.setExport('x', 1);
});
await m.link(() => {});
await m.evaluate();
assert.strictEqual(m.namespace.x, 1);
const vm = require('node:vm');
(async () => {
const m = new vm.SyntheticModule(['x'], () => {
m.setExport('x', 1);
});
await m.link(() => {});
await m.evaluate();
assert.strictEqual(m.namespace.x, 1);
})();
vm.compileFunction(code[, params[, options]])
#
code
<string> 要编译的函数体。params
<string[]> 包含函数所有参数的字符串数组。options
<Object>filename
<string> 指定此脚本生成的堆栈跟踪中使用的文件名。 默认值:''
。lineOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的行号偏移量。 默认值:0
。columnOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的第一行列号偏移量。 默认值:0
。cachedData
<Buffer> | <TypedArray> | <DataView> 为所提供的源提供可选的Buffer
或TypedArray
或DataView
,其中包含 V8 的代码缓存数据。produceCachedData
<boolean> 指定是否产生新的缓存数据。 默认值:false
。parsingContext
<Object> 应在其中编译所述函数的上下文隔离化的对象。contextExtensions
<Object[]> 包含要在编译时应用的上下文扩展集合(包含当前作用域的对象)的数组。 默认值:[]
。importModuleDynamically
<Function> 在调用import()
时在评估此模块期间调用。 如果未指定此选项,则调用import()
将使用ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
拒绝。 此选项是实验模块 API 的一部分,不应被视为稳定的。specifier
<string> 传给import()
的说明符function
<Function>importAssertions
<Object> 传给optionsExpression
可选参数的"assert"
值,如果没有提供值,则为空对象。- 返回: <Module Namespace Object> | <vm.Module> 建议返回
vm.Module
以利用错误跟踪,并避免包含then
函数导出的命名空间出现问题。
- 返回: <Function>
将给定的代码编译到提供的上下文中(如果没有提供上下文,则使用当前上下文),并返回它包装在具有给定 params
的函数中。
vm.createContext([contextObject[, options]])
#
contextObject
<Object>options
<Object>name
<string> 新创建的上下文的可读名称。 默认值:'VM Context i'
, 其中i
是创建的上下文的升序数字索引。origin
<string> 对应于新创建的用于显示目的的上下文的来源。 来源的格式应该像 URL,但只有协议、主机和端口(如果需要),就像URL
对象的url.origin
属性的值。 最值得注意的是,该字符串应省略尾部斜杠,因为它表示路径。 默认值:''
。codeGeneration
<Object>microtaskMode
<string> 如果设置为afterEvaluate
,则微任务(通过Promise
和async function
调度的任务)将在脚本运行到script.runInContext()
后立即运行。 在这种情况下,它们包含在timeout
和breakOnSigint
范围内。
- 返回: <Object> 上下文隔离化的对象。
如果给定 contextObject
,vm.createContext()
方法将准备那个对象,以便它可以用于调用 vm.runInContext()
或 script.runInContext()
。
在此类脚本中,contextObject
将是全局对象,保留其所有现有属性,但也具有任何标准全局对象具有的内置对象和函数。
在 vm 模块运行的脚本之外,全局变量将保持不变。
const vm = require('node:vm');
global.globalVar = 3;
const context = { globalVar: 1 };
vm.createContext(context);
vm.runInContext('globalVar *= 2;', context);
console.log(context);
// 打印: { globalVar: 2 }
console.log(global.globalVar);
// 打印: 3
如果省略 contextObject
(或显式地作为 undefined
传入),则将返回新的空的上下文隔离化的对象。
vm.createContext()
方法主要用于创建可用于运行多个脚本的单个上下文。
例如,如果模拟网络浏览器,则该方法可用于创建表示窗口全局对象的单个上下文,然后在该上下文中一起运行所有 <script>
标签。
提供的上下文的 name
和 origin
通过检查器 API 可见。
vm.isContext(object)
#
如果给定的 object
对象已使用 vm.createContext()
上下文隔离化,则返回 true
。
vm.measureMemory([options])
#
测量 V8 已知的内存并被当前 V8 隔离已知的所有上下文或主上下文使用。
返回的 Promise 可以解决的对象的格式特定于 V8 引擎,并且可能会从 V8 的一个版本更改为下一个版本。
返回的结果与 v8.getHeapSpaceStatistics()
返回的统计数据不同,vm.measureMemory()
测量的是 V8 引擎当前实例中每个 V8 特定上下文可访问的内存,而 v8.getHeapSpaceStatistics()
的结果测量的是当前V8中每个堆空间占用的内存实例。
const vm = require('node:vm');
// 测量主上下文使用的内存。
vm.measureMemory({ mode: 'summary' })
// 这和 vm.measureMemory() 一样
.then((result) => {
// 当前格式为:
// {
// total: {
// jsMemoryEstimate: 2418479, jsMemoryRange: [ 2418479, 2745799 ]
// }
// }
console.log(result);
});
const context = vm.createContext({ a: 1 });
vm.measureMemory({ mode: 'detailed', execution: 'eager' })
.then((result) => {
// 在此处引用上下文,
// 以便在测量完成之前不会对其进行 GC。
console.log(context.a);
// {
// total: {
// jsMemoryEstimate: 2574732,
// jsMemoryRange: [ 2574732, 2904372 ]
// },
// current: {
// jsMemoryEstimate: 2438996,
// jsMemoryRange: [ 2438996, 2768636 ]
// },
// other: [
// {
// jsMemoryEstimate: 135736,
// jsMemoryRange: [ 135736, 465376 ]
// }
// ]
// }
console.log(result);
});
vm.runInContext(code, contextifiedObject[, options])
#
code
<string> 要编译和运行的 JavaScript 代码。contextifiedObject
<Object> 编译和运行code
时将用作global
的上下文隔离化的对象。options
<Object> | <string>filename
<string> 指定此脚本生成的堆栈跟踪中使用的文件名。 默认值:'evalmachine.<anonymous>'
。lineOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的行号偏移量。 默认值:0
。columnOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的第一行列号偏移量。 默认值:0
。displayErrors
<boolean> 当为true
时,如果编译code
时出现Error
,则导致错误的代码行会附加到堆栈跟踪中。 默认值:true
。timeout
<integer> 指定终止执行前执行code
的毫秒数。 如果执行终止,则将抛出Error
。 此值必须是严格的正整数。breakOnSigint
<boolean> 如果为true
,则接收SIGINT
(Ctrl+C)将终止执行并抛出Error
。 已通过process.on('SIGINT')
附加的事件的现有句柄在脚本执行期间被禁用,但在此之后继续工作。 默认值:false
。cachedData
<Buffer> | <TypedArray> | <DataView> 为所提供的源提供可选的Buffer
或TypedArray
或DataView
,其中包含 V8 的代码缓存数据。importModuleDynamically
<Function> 在调用import()
时在评估此模块期间调用。 如果未指定此选项,则调用import()
将使用ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
拒绝。 此选项是实验模块 API 的一部分。 不建议在生产环境中使用它。specifier
<string> 传给import()
的说明符script
<vm.Script>importAssertions
<Object> 传给optionsExpression
可选参数的"assert"
值,如果没有提供值,则为空对象。- 返回: <Module Namespace Object> | <vm.Module> 建议返回
vm.Module
以利用错误跟踪,并避免包含then
函数导出的命名空间出现问题。
- 返回: <any> 脚本中执行的最后一条语句的结果。
vm.runInContext()
方法编译 code
,在 contextifiedObject
的上下文中运行它,然后返回结果。
运行代码无权访问本地作用域。
contextifiedObject
对象必须之前已经使用 vm.createContext()
方法上下文隔离化。
如果 options
是字符串,则指定文件名。
以下示例使用单个上下文隔离化的对象编译并执行不同的脚本:
const vm = require('node:vm');
const contextObject = { globalVar: 1 };
vm.createContext(contextObject);
for (let i = 0; i < 10; ++i) {
vm.runInContext('globalVar *= 2;', contextObject);
}
console.log(contextObject);
// 打印: { globalVar: 1024 }
vm.runInNewContext(code[, contextObject[, options]])
#
code
<string> 要编译和运行的 JavaScript 代码。contextObject
<Object> 将被上下文隔离化的对象。 如果为undefined
,则将创建新的对象。options
<Object> | <string>filename
<string> 指定此脚本生成的堆栈跟踪中使用的文件名。 默认值:'evalmachine.<anonymous>'
。lineOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的行号偏移量。 默认值:0
。columnOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的第一行列号偏移量。 默认值:0
。displayErrors
<boolean> 当为true
时,如果编译code
时出现Error
,则导致错误的代码行会附加到堆栈跟踪中。 默认值:true
。timeout
<integer> 指定终止执行前执行code
的毫秒数。 如果执行终止,则将抛出Error
。 此值必须是严格的正整数。breakOnSigint
<boolean> 如果为true
,则接收SIGINT
(Ctrl+C)将终止执行并抛出Error
。 已通过process.on('SIGINT')
附加的事件的现有句柄在脚本执行期间被禁用,但在此之后继续工作。 默认值:false
。contextName
<string> 新创建的上下文的可读名称。 默认值:'VM Context i'
, 其中i
是创建的上下文的升序数字索引。contextOrigin
<string> 对应于新创建的用于显示目的的上下文的来源。 来源的格式应该像 URL,但只有协议、主机和端口(如果需要),就像URL
对象的url.origin
属性的值。 最值得注意的是,该字符串应省略尾部斜杠,因为它表示路径。 默认值:''
。contextCodeGeneration
<Object>cachedData
<Buffer> | <TypedArray> | <DataView> 为所提供的源提供可选的Buffer
或TypedArray
或DataView
,其中包含 V8 的代码缓存数据。importModuleDynamically
<Function> 在调用import()
时在评估此模块期间调用。 如果未指定此选项,则调用import()
将使用ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
拒绝。 此选项是实验模块 API 的一部分。 不建议在生产环境中使用它。specifier
<string> 传给import()
的说明符script
<vm.Script>importAssertions
<Object> 传给optionsExpression
可选参数的"assert"
值,如果没有提供值,则为空对象。- 返回: <Module Namespace Object> | <vm.Module> 建议返回
vm.Module
以利用错误跟踪,并避免包含then
函数导出的命名空间出现问题。
microtaskMode
<string> 如果设置为afterEvaluate
,则微任务(通过Promise
和async function
调度的任务)将在脚本运行后立即运行。 在这种情况下,它们包含在timeout
和breakOnSigint
范围内。
- 返回: <any> 脚本中执行的最后一条语句的结果。
vm.runInNewContext()
首先将给定的 contextObject
上下文化(如果作为 undefined
传入,则创建新的 contextObject
),编译 code
,在创建的上下文中运行它,然后返回结果。
运行代码无权访问本地作用域。
如果 options
是字符串,则指定文件名。
以下示例编译并执行增加全局变量并设置新变量的代码。
这些全局变量包含在 contextObject
中。
const vm = require('node:vm');
const contextObject = {
animal: 'cat',
count: 2
};
vm.runInNewContext('count += 1; name = "kitty"', contextObject);
console.log(contextObject);
// 打印: { animal: 'cat', count: 3, name: 'kitty' }
vm.runInThisContext(code[, options])
#
code
<string> 要编译和运行的 JavaScript 代码。options
<Object> | <string>filename
<string> 指定此脚本生成的堆栈跟踪中使用的文件名。 默认值:'evalmachine.<anonymous>'
。lineOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的行号偏移量。 默认值:0
。columnOffset
<number> 指定在此脚本生成的堆栈跟踪中显示的第一行列号偏移量。 默认值:0
。displayErrors
<boolean> 当为true
时,如果编译code
时出现Error
,则导致错误的代码行会附加到堆栈跟踪中。 默认值:true
。timeout
<integer> 指定终止执行前执行code
的毫秒数。 如果执行终止,则将抛出Error
。 此值必须是严格的正整数。breakOnSigint
<boolean> 如果为true
,则接收SIGINT
(Ctrl+C)将终止执行并抛出Error
。 已通过process.on('SIGINT')
附加的事件的现有句柄在脚本执行期间被禁用,但在此之后继续工作。 默认值:false
。cachedData
<Buffer> | <TypedArray> | <DataView> 为所提供的源提供可选的Buffer
或TypedArray
或DataView
,其中包含 V8 的代码缓存数据。importModuleDynamically
<Function> 在调用import()
时在评估此模块期间调用。 如果未指定此选项,则调用import()
将使用ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
拒绝。 此选项是实验模块 API 的一部分。 不建议在生产环境中使用它。specifier
<string> 传给import()
的说明符script
<vm.Script>importAssertions
<Object> 传给optionsExpression
可选参数的"assert"
值,如果没有提供值,则为空对象。- 返回: <Module Namespace Object> | <vm.Module> 建议返回
vm.Module
以利用错误跟踪,并避免包含then
函数导出的命名空间出现问题。
- 返回: <any> 脚本中执行的最后一条语句的结果。
vm.runInThisContext()
编译 code
,在当前 global
的上下文中运行它并返回结果。
运行代码无权访问局部作用域,但可以访问当前 global
对象。
如果 options
是字符串,则指定文件名。
以下示例说明使用 vm.runInThisContext()
和 JavaScript eval()
函数来运行相同的代码:
const vm = require('node:vm');
let localVar = 'initial value';
const vmResult = vm.runInThisContext('localVar = "vm";');
console.log(`vmResult: '${vmResult}', localVar: '${localVar}'`);
// 打印: vmResult: 'vm', localVar: 'initial value'
const evalResult = eval('localVar = "eval";');
console.log(`evalResult: '${evalResult}', localVar: '${localVar}'`);
// 打印: evalResult: 'eval', localVar: 'eval'
因为 vm.runInThisContext()
无权访问本地作用域,所以 localVar
不变。
相比之下,eval()
确实有权访问本地作用域,因此值 localVar
已更改。
这样 vm.runInThisContext()
很像 间接 eval()
调用,例如 (0,eval)('code')
。
示例:在 VM 中运行 HTTP Server#
当使用 script.runInThisContext()
或 vm.runInThisContext()
时,代码在当前 V8 全局上下文中执行。
传给此 VM 上下文的代码将有自己的隔离作用域。
为了使用 node:http
模块运行简单的 web 服务器,传给上下文的代码必须要么自己调用 require('node:http')
,要么有对传给它的 node:http
模块的引用。
例如:
'use strict';
const vm = require('node:vm');
const code = `
((require) => {
const http = require('node:http');
http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Hello World\\n');
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
})`;
vm.runInThisContext(code)(require);
上述案例中的 require()
与其传入的上下文共享状态。
当执行不受信任的代码时,这可能会带来风险,例如以不需要的方式更改上下文中的对象。
上下文隔离化一个对象意味着什么?#
在 Node.js 中执行的所有 JavaScript 都在 "上下文" 的作用域内运行。 根据 V8 嵌入器指南:
在 V8 中,上下文是一个执行环境,它允许单独的、不相关的 JavaScript 应用程序在 V8 的单个实例中运行。 必须明确指定要在其中运行任何 JavaScript 代码的上下文。
当方法 vm.createContext()
被调用时,contextObject
参数(或者新创建的对象,如果 contextObject
是 undefined
)在内部与 V8 上下文的新实例相关联。
这个 V8 上下文使用 node:vm
模块的方法提供了 code
运行,它可以在隔离的全局环境中运行。
创建 V8 上下文并将其与 contextObject
相关联的过程就是本文档所指的“上下文隔离化”对象。
与异步任务和 Promise 的超时交互#
Promise
和 async function
可以异步地调度 JavaScript 引擎运行的任务。
默认情况下,这些任务在当前堆栈上的所有 JavaScript 函数执行完毕后运行。
这允许转义 timeout
和 breakOnSigint
选项的功能。
例如,以下代码由 vm.runInNewContext()
执行,超时时间为 5 毫秒,它安排了一个无限循环在 promise 解决后运行。
计划的循环永远不会被超时中断:
const vm = require('node:vm');
function loop() {
console.log('entering loop');
while (1) console.log(Date.now());
}
vm.runInNewContext(
'Promise.resolve().then(() => loop());',
{ loop, console },
{ timeout: 5 }
);
// 这是在 'entering loop' 之前打印 (!)
console.log('done executing');
这可以通过将 microtaskMode: 'afterEvaluate'
传给创建 Context
的代码来解决:
const vm = require('node:vm');
function loop() {
while (1) console.log(Date.now());
}
vm.runInNewContext(
'Promise.resolve().then(() => loop());',
{ loop, console },
{ timeout: 5, microtaskMode: 'afterEvaluate' }
);
在这种情况下,通过 promise.then()
调度的微任务将在从 vm.runInNewContext()
返回之前运行,并会被 timeout
功能中断。
这仅适用于在 vm.Context
中运行的代码,因此例如 vm.runInThisContext()
不采用此选项。
Promise 回调被输入到创建它们的上下文的微任务队列中。
例如,如果在上面的例子中 () => loop()
只被 loop
替换,那么 loop
将被推入全局微任务队列,因为它是来自外部(主)上下文的函数,因此也将能够逃脱超时。
如 process.nextTick()
、queueMicrotask()
、setTimeout()
、setImmediate()
等异步调度函数。
在 vm.Context
中可用,传给它们的函数将被添加到全局队列中,由所有上下文共享。
因此,传给这些函数的回调也无法通过超时控制。