Node.js v21.7.2 文档
- Node.js v21.7.2
-
► 目录
- VM(执行 JavaScript)
- 类:
vm.Script
- 类:
vm.Module
- 类:
vm.SourceTextModule
- 类:
vm.SyntheticModule
vm.compileFunction(code[, params[, options]])
vm.constants
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 服务器
- “上下文化”一个对象意味着什么?
- 异步任务和 Promise 的超时交互
- 编译 API 中动态
import()
的支持
- 类:
- VM(执行 JavaScript)
-
► 索引
- 断言测试
- 异步上下文跟踪
- 异步钩子
- 缓冲区
- C++ 附加模块
- 使用 Node-API 的 C/C++ 附加模块
- C++ 嵌入器 API
- 子进程
- 集群
- 命令行选项
- 控制台
- Corepack
- 加密
- 调试器
- 已弃用的 API
- 诊断通道
- DNS
- 域
- 错误
- 事件
- 文件系统
- 全局对象
- HTTP
- HTTP/2
- HTTPS
- 检查器
- 国际化
- 模块:CommonJS 模块
- 模块:ECMAScript 模块
- 模块:
node:module
API - 模块:包
- 网络
- 操作系统
- 路径
- 性能钩子
- 权限
- 进程
- Punycode
- 查询字符串
- 读取行
- REPL
- 报告
- 单一可执行应用程序
- 流
- 字符串解码器
- 测试运行器
- 计时器
- TLS/SSL
- 跟踪事件
- TTY
- UDP/数据报
- URL
- 实用程序
- V8
- VM
- WASI
- Web Crypto API
- Web Streams API
- 工作线程
- Zlib
- ► 其他版本
- ► 选项
VM(执行 JavaScript)#
源代码: 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); // Contextify the object.
const code = 'x += 40; var y = 17;';
// `x` and `y` are global variables in the context.
// Initially, x has the value 2 because that is the value of context.x.
vm.runInContext(code, context);
console.log(context.x); // 42
console.log(context.y); // 17
console.log(x); // 1; y is not defined.
类: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> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用import()
时,此脚本评估期间模块应如何加载。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅 编译 API 中动态import()
的支持。
如果 options
是一个字符串,则它指定文件名。
创建一个新的 vm.Script
对象会编译 code
但不会运行它。编译后的 vm.Script
可以稍后多次运行。code
不绑定到任何全局对象;相反,它在每次运行之前绑定,仅针对该运行。
script.cachedDataRejected
#
当 cachedData
被提供用于创建 vm.Script
时,此值将被设置为 true
或 false
,具体取决于 V8 是否接受数据。否则,该值为 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` contains fully compiled function `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);
// Prints: { 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.origin
属性的值URL
对象。最值得注意的是,此字符串应省略尾部斜杠,因为这表示路径。默认值:''
。contextCodeGeneration
<对象>microtaskMode
<字符串> 如果设置为afterEvaluate
,microtasks(通过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);
// Prints: [{ 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
script.sourceMapURL
#
当脚本从包含源映射魔法注释的源代码编译时,此属性将设置为源映射的 URL。
import vm from 'node:vm';
const script = new vm.Script(`
function myFunc() {}
//# sourceMappingURL=sourcemap.json
`);
console.log(script.sourceMapURL);
// Prints: sourcemap.json
const vm = require('node:vm');
const script = new vm.Script(`
function myFunc() {}
//# sourceMappingURL=sourcemap.json
`);
console.log(script.sourceMapURL);
// Prints: sourcemap.json
类: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,
});
// Step 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 });
// Step 2
//
// "Link" the imported dependencies of this Module to it.
//
// The provided linking callback (the "linker") accepts two arguments: 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.
//
// The link() method returns a Promise that will be resolved when all the
// Promises returned by the linker resolve.
//
// 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);
// Step 3
//
// Evaluate the Module. The evaluate() method returns a promise which will
// resolve after the module has finished evaluating.
// Prints 42.
await bar.evaluate();
const vm = require('node:vm');
const contextifiedObject = vm.createContext({
secret: 42,
print: console.log,
});
(async () => {
// Step 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 });
// Step 2
//
// "Link" the imported dependencies of this Module to it.
//
// The provided linking callback (the "linker") accepts two arguments: 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.
//
// The link() method returns a Promise that will be resolved when all the
// Promises returned by the linker resolve.
//
// 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);
// Step 3
//
// Evaluate the Module. The evaluate() method returns a promise which will
// resolve after the module has finished evaluating.
// Prints 42.
await bar.evaluate();
})();
module.dependencySpecifiers
#
此模块所有依赖项的说明符。返回的数组被冻结,不允许对其进行任何更改。
对应于 ECMAScript 规范中 循环模块记录 的 [[RequestedModules]]
字段。
module.error
#
如果 module.status
为 'errored'
,则此属性包含模块在评估期间抛出的异常。如果状态为其他任何值,访问此属性将导致抛出异常。
由于可能与 throw undefined;
模糊不清,因此在没有抛出异常的情况下,不能使用 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'; // ^^^^^ the module specifier
-
referencingModule
<vm.Module> 调用link()
的Module
对象。 -
extra
<Object> -
返回值: <vm.Module> | <Promise>
-
- 返回值: <Promise>
链接模块依赖项。此方法必须在评估之前调用,并且每个模块只能调用一次。
该函数预计将返回一个 Module
对象或一个最终解析为 Module
对象的 Promise
。返回的 Module
必须满足以下两个不变式
- 它必须属于与父
Module
相同的上下文。 - 它的
status
必须不是'errored'
。
如果返回的 Module
的 status
为 'unlinked'
,则此方法将使用相同的提供的 linker
函数递归调用返回的 Module
。
link()
返回一个 Promise
,当所有链接实例都解析为有效的 Module
时,该 Promise
将被解决,如果链接器函数抛出异常或返回无效的 Module
,则该 Promise
将被拒绝。
链接器函数大致对应于 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()
时,在评估此模块期间如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅 编译 API 中对动态import()
的支持。
创建一个新的 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) {
// Note: this object is created in the top context. As such,
// Object.getPrototypeOf(import.meta.prop) points to the
// Object.prototype in the top context rather than that in
// the contextified object.
meta.prop = {};
},
});
// Since module has no dependencies, the linker function will never be called.
await module.link(() => {});
await module.evaluate();
// Now, Object.prototype.secret will be equal to 42.
//
// To fix this problem, replace
// meta.prop = {};
// above with
// 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) {
// Note: this object is created in the top context. As such,
// Object.getPrototypeOf(import.meta.prop) points to the
// Object.prototype in the top context rather than that in
// the contextified object.
meta.prop = {};
},
});
// Since module has no dependencies, the linker function will never be called.
await module.link(() => {});
await module.evaluate();
// Now, Object.prototype.secret will be equal to 42.
//
// To fix this problem, replace
// meta.prop = {};
// above with
// meta.prop = vm.runInContext('{}', contextifiedObject);
})();
sourceTextModule.createCachedData()
#
- 返回:<Buffer>
创建一个代码缓存,可与SourceTextModule
构造函数的cachedData
选项一起使用。返回一个Buffer
。此方法可以在模块被评估之前被调用任意次数。
SourceTextModule
的代码缓存不包含任何 JavaScript 可观察状态。代码缓存可以安全地与脚本源一起保存,并用于多次构造新的SourceTextModule
实例。
SourceTextModule
源中的函数可以标记为延迟编译,它们不会在SourceTextModule
构造时被编译。这些函数将在第一次调用时被编译。代码缓存序列化 V8 当前知道的关于SourceTextModule
的元数据,这些元数据可以用来加速未来的编译。
// Create an initial module
const module = new vm.SourceTextModule('const a = 1;');
// Create cached data from this module
const cachedData = module.createCachedData();
// Create a new module using the cached data. The code must be the same.
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);
});
// Use `module` in linking...
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
,或使用 V8 代码缓存数据的DataView
。这必须由先前对vm.compileFunction()
的调用使用相同的code
和params
生成。produceCachedData
<boolean> 指定是否生成新的缓存数据。默认值:false
。parsingContext
<Object> 所述函数应在其中编译的上下文化对象。contextExtensions
<Object[]> 包含一系列上下文扩展(封装当前作用域的对象)的数组,在编译时应用。默认值:[]
。
importModuleDynamically
<Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用import()
时,此函数评估期间模块的加载方式。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅 编译 API 中动态import()
的支持。- 返回值:<Function>
将给定代码编译到提供的上下文中(如果未提供上下文,则使用当前上下文),并将其包装在具有给定 params
的函数中返回。
vm.constants
#
返回一个包含 VM 操作常用常量的对象。
vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER
#
一个常量,可以用作 vm.Script
和 vm.compileFunction()
的 importModuleDynamically
选项,以便 Node.js 使用主上下文中的默认 ESM 加载器来加载请求的模块。
有关详细信息,请参阅 编译 API 中动态 import()
的支持。
vm.createContext([contextObject[, options]])
#
contextObject
<Object>options
<Object>name
<string> 新创建上下文的易读名称。默认值:'VM Context i'
,其中i
是创建上下文的递增数字索引。origin
<string> Origin 用于显示目的,对应于新创建的上下文。该 origin 应像 URL 一样格式化,但仅包含方案、主机和端口(如果需要),类似于url.origin
属性的URL
对象的值。最值得注意的是,此字符串应省略尾部斜杠,因为该斜杠表示路径。默认值:''
。codeGeneration
<Object>microtaskMode
<string> 如果设置为afterEvaluate
,微任务(通过Promise
和async function
调度的任务)将在脚本通过script.runInContext()
运行后立即运行。在这种情况下,它们包含在timeout
和breakOnSigint
范围内。importModuleDynamically
<Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在没有引用脚本或模块的情况下,在该上下文中调用import()
时,模块应如何加载。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅 编译 API 中对动态import()
的支持。
- 返回值:<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);
// Prints: { globalVar: 2 }
console.log(global.globalVar);
// Prints: 3
如果省略 contextObject
(或显式地将其传递为 undefined
),则将返回一个新的、空的 上下文化 对象。
vm.createContext()
方法主要用于创建单个上下文,该上下文可用于运行多个脚本。例如,如果模拟 Web 浏览器,该方法可用于创建表示窗口全局对象的单个上下文,然后在该上下文内一起运行所有 <script>
标签。
提供的上下文的 name
和 origin
通过 Inspector API 可见。
vm.isContext(object)
#
如果给定的 object
对象已使用 上下文化(使用 vm.createContext()
),则返回 true
。
vm.measureMemory([options])
#
测量 V8 已知并由当前 V8 隔离区中所有已知上下文或主上下文使用的内存。
options
<Object> 可选。- 返回值:<Promise> 如果内存测量成功,promise 将解析为包含有关内存使用情况信息的 object。否则,它将被
ERR_CONTEXT_NOT_INITIALIZED
错误拒绝。
返回的 Promise 可能解析为的 object 的格式特定于 V8 引擎,并且可能在 V8 的不同版本之间发生变化。
返回的结果与v8.getHeapSpaceStatistics()
返回的统计信息不同,因为vm.measureMemory()
测量的是当前 V8 引擎实例中每个 V8 特定上下文可访问的内存,而v8.getHeapSpaceStatistics()
的结果测量的是当前 V8 实例中每个堆空间占用的内存。
const vm = require('node:vm');
// Measure the memory used by the main context.
vm.measureMemory({ mode: 'summary' })
// This is the same as vm.measureMemory()
.then((result) => {
// The current format is:
// {
// total: {
// jsMemoryEstimate: 2418479, jsMemoryRange: [ 2418479, 2745799 ]
// }
// }
console.log(result);
});
const context = vm.createContext({ a: 1 });
vm.measureMemory({ mode: 'detailed', execution: 'eager' })
.then((result) => {
// Reference the context here so that it won't be GC'ed
// until the measurement is complete.
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> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用import()
时,在评估此脚本期间如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅编译 API 中对动态import()
的支持。
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);
// Prints: { 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.origin
属性的值URL
对象。最值得注意的是,此字符串应省略尾部斜杠,因为这表示路径。默认值:''
。contextCodeGeneration
<对象>cachedData
<Buffer> | <TypedArray> | <DataView> 提供可选的Buffer
或TypedArray
,或DataView
,其中包含 V8 的代码缓存数据,用于提供的源代码。importModuleDynamically
<Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用import()
时,在评估此脚本期间如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅编译 API 中对动态import()
的支持。microtaskMode
<字符串> 如果设置为afterEvaluate
,microtasks(通过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);
// Prints: { 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> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用于指定在调用import()
时,在评估此脚本期间如何加载模块。此选项是实验性模块 API 的一部分。我们不建议在生产环境中使用它。有关详细信息,请参阅编译 API 中对动态import()
的支持。
- 返回值:<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}'`);
// Prints: vmResult: 'vm', localVar: 'initial value'
const evalResult = eval('localVar = "eval";');
console.log(`evalResult: '${evalResult}', localVar: '${localVar}'`);
// Prints: evalResult: 'eval', localVar: 'eval'
由于 vm.runInThisContext()
无法访问局部作用域,因此 localVar
不会改变。相反,eval()
可以访问局部作用域,因此 localVar
的值会改变。这样,vm.runInThisContext()
就类似于 间接 eval()
调用,例如 (0,eval)('code')
。
示例:在 VM 中运行 HTTP 服务器#
当使用 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 },
);
// This is printed *before* '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
将被推入全局微任务队列,因为它来自外部(主)上下文,因此也将能够逃避超时。
如果在 vm.Context
中提供了异步调度函数,例如 process.nextTick()
、queueMicrotask()
、setTimeout()
、setImmediate()
等,传递给它们的函数将被添加到全局队列中,这些队列由所有上下文共享。因此,传递给这些函数的回调也不能通过超时进行控制。
编译 API 中动态 import()
的支持#
以下 API 支持 importModuleDynamically
选项,以在 vm 模块编译的代码中启用动态 import()
。
new vm.Script
vm.compileFunction()
new vm.SourceTextModule
vm.runInThisContext()
vm.runInContext()
vm.runInNewContext()
vm.createContext()
此选项仍是实验性模块 API 的一部分。我们不建议在生产环境中使用它。
当未指定或未定义 importModuleDynamically
选项时#
如果未指定此选项,或其值为 undefined
,包含 import()
的代码仍然可以被 vm API 编译,但当编译后的代码执行并实际调用 import()
时,结果将拒绝并抛出 ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
错误。
当 importModuleDynamically
为 vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER
时#
此选项目前不支持 vm.SourceTextModule
。
使用此选项,当在编译后的代码中启动 import()
时,Node.js 将使用主上下文中的默认 ESM 加载器来加载请求的模块并将其返回给正在执行的代码。
这使编译的代码能够访问 Node.js 内置模块,例如 fs
或 http
。如果代码在不同的上下文中执行,请注意,从主上下文加载的模块创建的对象仍然来自主上下文,而不是新上下文中的内置类实例。
const { Script, constants } = require('node:vm');
const script = new Script(
'import("node:fs").then(({readFile}) => readFile instanceof Function)',
{ importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER });
// false: URL loaded from the main context is not an instance of the Function
// class in the new context.
script.runInNewContext().then(console.log);
import { Script, constants } from 'node:vm';
const script = new Script(
'import("node:fs").then(({readFile}) => readFile instanceof Function)',
{ importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER });
// false: URL loaded from the main context is not an instance of the Function
// class in the new context.
script.runInNewContext().then(console.log);
此选项还允许脚本或函数加载用户模块。
import { Script, constants } from 'node:vm';
import { resolve } from 'node:path';
import { writeFileSync } from 'node:fs';
// Write test.js and test.txt to the directory where the current script
// being run is located.
writeFileSync(resolve(import.meta.dirname, 'test.mjs'),
'export const filename = "./test.json";');
writeFileSync(resolve(import.meta.dirname, 'test.json'),
'{"hello": "world"}');
// Compile a script that loads test.mjs and then test.json
// as if the script is placed in the same directory.
const script = new Script(
`(async function() {
const { filename } = await import('./test.mjs');
return import(filename, { with: { type: 'json' } })
})();`,
{
filename: resolve(import.meta.dirname, 'test-with-default.js'),
importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
});
// { default: { hello: 'world' } }
script.runInThisContext().then(console.log);
const { Script, constants } = require('node:vm');
const { resolve } = require('node:path');
const { writeFileSync } = require('node:fs');
// Write test.js and test.txt to the directory where the current script
// being run is located.
writeFileSync(resolve(__dirname, 'test.mjs'),
'export const filename = "./test.json";');
writeFileSync(resolve(__dirname, 'test.json'),
'{"hello": "world"}');
// Compile a script that loads test.mjs and then test.json
// as if the script is placed in the same directory.
const script = new Script(
`(async function() {
const { filename } = await import('./test.mjs');
return import(filename, { with: { type: 'json' } })
})();`,
{
filename: resolve(__dirname, 'test-with-default.js'),
importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
});
// { default: { hello: 'world' } }
script.runInThisContext().then(console.log);
使用主上下文中的默认加载器加载用户模块有一些注意事项。
- 要解析的模块将相对于传递给
vm.Script
或vm.compileFunction()
的filename
选项。解析可以处理绝对路径或 URL 字符串形式的filename
。如果filename
是既不是绝对路径也不是 URL 的字符串,或者它未定义,则解析将相对于进程的当前工作目录。在vm.createContext()
的情况下,解析始终相对于当前工作目录,因为此选项仅在没有引用脚本或模块时使用。 - 对于解析为特定路径的任何给定
filename
,一旦进程成功从该路径加载特定模块,结果可能会被缓存,并且随后从同一路径加载同一模块将返回相同的内容。如果filename
是 URL 字符串,则如果它具有不同的搜索参数,则不会命中缓存。对于不是 URL 字符串的filename
,目前无法绕过缓存行为。
当 importModuleDynamically
是一个函数#
当 importModuleDynamically
是一个函数时,它将在编译代码中调用 import()
时被调用,以便用户自定义如何编译和评估请求的模块。目前,Node.js 实例必须使用 --experimental-vm-modules
标志启动才能使此选项生效。如果未设置标志,则此回调将被忽略。如果评估的代码实际调用 import()
,则结果将拒绝,并出现 ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG
错误。
回调 importModuleDynamically(specifier, referrer, importAttributes)
具有以下签名
specifier
<string> 传递给import()
的说明符referrer
<vm.Script> | <Function> | <vm.SourceTextModule> | <Object> 引用者是new vm.Script
、vm.runInThisContext
、vm.runInContext
和vm.runInNewContext
的编译后的vm.Script
。它是vm.compileFunction
的编译后的Function
,是new vm.SourceTextModule
的编译后的vm.SourceTextModule
,以及vm.createContext()
的上下文Object
。importAttributes
<Object> 传递给optionsExpression
可选参数的"with"
值,或者如果未提供值,则为空对象。- 返回值:<Module Namespace Object> | <vm.Module> 建议返回
vm.Module
,以便利用错误跟踪,并避免包含then
函数导出名的命名空间问题。
// This script must be run with --experimental-vm-modules.
import { Script, SyntheticModule } from 'node:vm';
const script = new Script('import("foo.json", { with: { type: "json" } })', {
async importModuleDynamically(specifier, referrer, importAttributes) {
console.log(specifier); // 'foo.json'
console.log(referrer); // The compiled script
console.log(importAttributes); // { type: 'json' }
const m = new SyntheticModule(['bar'], () => { });
await m.link(() => { });
m.setExport('bar', { hello: 'world' });
return m;
},
});
const result = await script.runInThisContext();
console.log(result); // { bar: { hello: 'world' } }
// This script must be run with --experimental-vm-modules.
const { Script, SyntheticModule } = require('node:vm');
(async function main() {
const script = new Script('import("foo.json", { with: { type: "json" } })', {
async importModuleDynamically(specifier, referrer, importAttributes) {
console.log(specifier); // 'foo.json'
console.log(referrer); // The compiled script
console.log(importAttributes); // { type: 'json' }
const m = new SyntheticModule(['bar'], () => { });
await m.link(() => { });
m.setExport('bar', { hello: 'world' });
return m;
},
});
const result = await script.runInThisContext();
console.log(result); // { bar: { hello: 'world' } }
})();