Node.js v21.7.2 文件
- Node.js v21.7.2
-
► 目錄
- VM(執行 JavaScript)
- 類別:
vm.Script
- 類別:
vm.Module
- 類別:
vm.SourceTextModule
- 類別:
vm.SyntheticModule
vm.compileFunction(代碼[, 參數[, 選項]])
vm.常數
vm.createContext([contextObject[, 選項]])
vm.isContext(物件)
vm.measureMemory([選項])
vm.runInContext(代碼, contextifiedObject[, 選項])
vm.runInNewContext(代碼[, contextObject[, 選項]])
vm.runInThisContext(代碼[, 選項])
- 範例:在 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
- 查詢字串
- Readline
- 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 Context 中執行程式碼。這表示呼叫的程式碼具有與呼叫程式碼不同的全域物件。
可以透過將物件背景化來提供背景。呼叫的程式碼會將背景中的任何屬性視為全域變數。呼叫的程式碼所造成的任何全域變數變更都會反映在背景物件中。
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
<數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值:0
。cachedData
<Buffer> | <TypedArray> | <DataView> 提供一個選用的Buffer
或TypedArray
,或具有 V8 的程式碼快取資料的DataView
,供應來源使用。提供時,cachedDataRejected
值會設定為true
或false
,視 V8 是否接受資料而定。produceCachedData
<布林> 當true
且沒有cachedData
時,V8 會嘗試產生code
的程式碼快取資料。成功時,會產生一個具有 V8 程式碼快取資料的Buffer
,並儲存在傳回的vm.Script
執行個體的cachedData
屬性中。cachedDataProduced
值會設定為true
或false
,視是否成功產生程式碼快取資料而定。此選項已過時,建議使用script.createCachedData()
。預設值:false
。importModuleDynamically
<函式> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用於指定在呼叫import()
時,評估此腳本期間應如何載入模組。此選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。如需詳細資訊,請參閱 編譯 API 中動態import()
的支援。
如果 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` contains fully compiled function `add()`.
script.runInContext(contextifiedObject[, options])
#
contextifiedObject
<Object> 由vm.createContext()
方法傳回的 context 化 物件。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,但僅包含必要的 scheme、主機和埠,例如url.origin
屬性的URL
物件的值。最重要的是,此字串應省略尾斜線,因為它表示路徑。預設值:''
。contextCodeGeneration
<Object>microtaskMode
<字串> 如果設為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);
// 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.Module
物件上的操作本質上是非同步的,這與 vm.Script
物件的同步性質相反。使用「非同步」函式有助於操作 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
物件或 Promise
,最終會解析為 Module
物件。傳回的 Module
必須符合下列兩個不變式
- 它必須屬於與父
Module
相同的內容。 - 它的
status
不能是'errored'
。
如果傳回的 Module
的 status
是 'unlinked'
,此方法會以相同的提供 linker
函式,遞迴呼叫傳回的 Module
。
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 模組程式碼選項
identifier
<string> 堆疊追蹤中使用的字串。預設:'vm:module(i)'
,其中i
是特定於內容的遞增索引。cachedData
<Buffer> | <TypedArray> | <DataView> 提供一個選用的Buffer
或TypedArray
,或DataView
,其中包含 V8 的程式碼快取資料,供應給程式碼來源。code
必須與建立此cachedData
的模組相同。context
<Object> 內容化 的物件,由vm.createContext()
方法傳回,用於編譯並評估此Module
。如果未指定內容,則會在目前的執行內容中評估模組。lineOffset
<整數> 指定由這個Module
產生的堆疊追蹤中顯示的行號偏移量。預設:0
。columnOffset
<整數> 指定由這個Module
產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設:0
。initializeImportMeta
<函式> 在評估這個Module
時呼叫,以初始化import.meta
。meta
<import.meta>module
<vm.SourceTextModule>
importModuleDynamically
<函式> 用於指定在呼叫import()
時,在評估這個模組時應如何載入模組。此選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。有關詳細資訊,請參閱 編譯 API 中對動態import()
的支援。
建立新的 SourceTextModule
實例。
指定給 import.meta
物件的屬性(為物件)可能允許模組存取指定 context
之外的資訊。使用 vm.runInContext()
在特定 context 中建立物件。
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> 在評估模組時呼叫。選項
建立新的 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
<字串> 要編譯的函式主體。params
<字串陣列> 包含函式所有參數的字串陣列。options
<Object>filename
<字串> 指定此腳本產生的堆疊追蹤中所使用的檔案名稱。預設:''
。lineOffset
<number> 指定此腳本產生的堆疊追蹤中顯示的行號偏移量。預設:0
。columnOffset
<數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值:0
。cachedData
<Buffer> | <TypedArray> | <DataView> 提供一個選擇性的Buffer
或TypedArray
,或DataView
,其中包含 V8 的程式碼快取資料,供應來源使用。這必須是由先前的vm.compileFunction()
呼叫產生,且具有相同的code
和params
。produceCachedData
<布林值> 指定是否產生新的快取資料。預設值:false
。parsingContext
<物件> 已設定語境的物件,指定的函式應編譯在其中。contextExtensions
<物件陣列> 包含一組語境擴充功能的陣列(包裝目前範圍的物件),在編譯時套用。預設值:[]
。
importModuleDynamically
<函式> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用於指定在呼叫import()
時,在評估此函式期間應如何載入模組。此選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。如需詳細資訊,請參閱 編譯 API 中對動態import()
的支援。- 傳回:<函式>
將提供的程式碼編譯到提供的語境中(如果未提供語境,則使用目前的語境),並將其包裝在具有指定 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 對應於新建立的內容以供顯示。來源應格式化為 URL,但僅包含必要的 scheme、主機和埠,例如url.origin
屬性的URL
物件的值。最重要的是,這個字串應省略尾斜線,因為那表示路徑。預設值:''
。codeGeneration
<Object>microtaskMode
<字串> 如果設為afterEvaluate
,微任務(透過Promise
和async function
排定的任務)將在腳本透過script.runInContext()
執行後立即執行。在這種情況下,它們會包含在timeout
和breakOnSigint
範圍內。importModuleDynamically
<函式> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> 用於指定在這個內容中呼叫import()
時,應如何載入模組,而沒有參考腳本或模組。這個選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。有關詳細資訊,請參閱 編譯 API 中動態import()
的支援。
- 傳回:<物件> 語境化物件。
如果給定 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()
方法主要用於建立一個單一內容,可以用來執行多個指令碼。例如,如果模擬一個網頁瀏覽器,這個方法可以用來建立一個代表視窗全域物件的單一內容,然後在這個內容中一起執行所有 <script>
標籤。
提供的內容 name
和 origin
會透過 Inspector API 可見。
vm.isContext(object)
#
如果給定的 object
物件已使用 vm.createContext()
語境化,則傳回 true
。
vm.measureMemory([options])
#
測量 V8 已知的記憶體,以及當前 V8 隔離或主內容所使用的所有內容。
options
<Object> 選擇性。- 傳回:<Promise>如果成功測量記憶體,承諾將會解析為包含記憶體使用資訊的物件。否則,它將會以
ERR_CONTEXT_NOT_INITIALIZED
錯誤拒絕。
傳回的承諾可能解析的物件格式特定於 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
<數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值: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
<數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值: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,但僅包含必要的 scheme、主機和埠,例如url.origin
屬性的URL
物件的值。最重要的是,此字串應省略尾斜線,因為它表示路徑。預設值:''
。contextCodeGeneration
<Object>cachedData
<Buffer> | <TypedArray> | <DataView>提供一個選用的Buffer
或TypedArray
,或DataView
,其中包含 V8 的程式碼快取資料,供應給的來源。importModuleDynamically
<Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER>用於指定在呼叫import()
時,在評估此腳本期間應如何載入模組。此選項是實驗性模組 API 的一部分。我們不建議在生產環境中使用它。有關詳細資訊,請參閱編譯 API 中動態import()
的支援。microtaskMode
<字串> 如果設為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);
// 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
<數字> 指定此腳本產生的堆疊追蹤中顯示的第一行欄位號碼偏移量。預設值: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
模組執行簡單的網路伺服器,傳遞給環境的程式碼必須自行呼叫 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 Embedder's Guide
在 V8 中,環境是一個執行環境,讓不同的、無關的 JavaScript 應用程式可以在 V8 的單一執行個體中執行。您必須明確指定要執行任何 JavaScript 程式碼的環境。
當呼叫 vm.createContext()
方法時,contextObject
參數(或如果 contextObject
為 undefined
則為新建立的物件)會在內部與 V8 環境的新執行個體關聯。此 V8 環境會提供使用 node:vm
模組方法執行的 code
一個孤立的全球環境,讓它可以在其中執行。建立 V8 環境並將其與 contextObject
關聯的程序就是本文檔所稱的「將物件環境化」。
逾時互動與非同步工作和承諾#
Promise
和 async function
可以安排由 JavaScript 引擎非同步執行的任務。預設情況下,這些任務會在當前堆疊中的所有 JavaScript 函式執行完畢後執行。這允許跳脫 timeout
和 breakOnSigint
選項的功能。
例如,以下由 vm.runInNewContext()
執行的程式碼,逾時時間為 5 毫秒,安排一個無限迴圈在承諾解決後執行。已安排的迴圈絕不會因逾時而中斷
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()
沒有這個選項。
承諾回呼會進入建立它們的內容的微任務佇列。例如,如果在上述範例中將 () => loop()
替換為 loop
,則 loop
會推入到全域微任務佇列,因為它是一個來自外部(主)內容的函式,因此也能跳脫逾時。
如果非同步安排函式(例如 process.nextTick()
、queueMicrotask()
、setTimeout()
、setImmediate()
等)在 vm.Context
內部提供,傳遞給它們的函式會新增到全域佇列,而這些佇列是由所有內容共用的。因此,傳遞給這些函式的回呼也無法透過逾時控制。
編譯 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
。如果程式碼在不同的內容中執行,請注意,從主內容載入的模組所建立的物件仍來自主內容,而不是新內容中內建類別的 instanceof
。
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
<字串> 傳遞給import()
的指定元referrer
<vm.Script> | <函式> | <vm.SourceTextModule> | <物件> 對於new vm.Script
、vm.runInThisContext
、vm.runInContext
和vm.runInNewContext
,參照者是已編譯的vm.Script
。對於vm.compileFunction
,參照者是已編譯的函式
;對於new vm.SourceTextModule
,參照者是已編譯的vm.SourceTextModule
;對於vm.createContext()
,參照者是內容物件
。importAttributes
<物件> 傳遞給optionsExpression
選用參數的"with"
值,或是在未提供值的情況下傳遞空物件。- 傳回:<模組命名空間物件> | <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' } }
})();