堆疊追蹤 API
V8 中拋出的所有內部錯誤都會在建立時擷取堆疊追蹤。此堆疊追蹤可透過非標準的 error.stack
屬性從 JavaScript 存取。V8 也有各種掛鉤,用於控制如何收集和格式化堆疊追蹤,以及允許自訂錯誤也收集堆疊追蹤。本文件概述 V8 的 JavaScript 堆疊追蹤 API。
基本堆疊追蹤 #
預設情況下,V8 拋出的幾乎所有錯誤都有 stack
屬性,其中包含格式化為字串的最上方 10 個堆疊框架。以下是完全格式化堆疊追蹤的範例
ReferenceError: FAIL is not defined
at Constraint.execute (deltablue.js:525:2)
at Constraint.recalculate (deltablue.js:424:21)
at Planner.addPropagate (deltablue.js:701:6)
at Constraint.satisfy (deltablue.js:184:15)
at Planner.incrementalAdd (deltablue.js:591:21)
at Constraint.addConstraint (deltablue.js:162:10)
at Constraint.BinaryConstraint (deltablue.js:346:7)
at Constraint.EqualityConstraint (deltablue.js:515:38)
at chainTest (deltablue.js:807:6)
at deltaBlue (deltablue.js:879:2)
堆疊追蹤會在建立錯誤時收集,而且無論錯誤在哪裡或拋出幾次,堆疊追蹤都相同。我們收集 10 個框架,因為這通常足夠有用,但不會多到對效能有明顯的負面影響。您可以透過設定變數來控制要收集多少個堆疊框架
Error.stackTraceLimit
將其設定為 0
會停用堆疊追蹤收集。任何有限整數值都可以用作要收集的最大框架數。將其設定為 Infinity
表示會收集所有框架。此變數只會影響目前的內容;必須為需要不同值的每個內容明確設定。 (請注意,在 V8 術語中稱為「內容」的東西對應到 Google Chrome 中的頁面或 <iframe>
)。若要設定影響所有內容的不同預設值,請使用下列 V8 命令列旗標
--stack-trace-limit <value>
若要在執行 Google Chrome 時將此旗標傳遞給 V8,請使用
--js-flags='--stack-trace-limit <value>'
非同步堆疊追蹤 #
--async-stack-traces
旗標(自 V8 v7.3 起預設開啟)啟用新的 零成本非同步堆疊追蹤,它會使用非同步堆疊框架(即程式碼中的 await
位置)豐富 Error
實例的 stack
屬性。這些非同步框架會在 stack
字串中標示為 async
ReferenceError: FAIL is not defined
at bar (<anonymous>)
at async foo (<anonymous>)
撰寫本文時,此功能僅限於 await
位置、Promise.all()
和 Promise.any()
,因為在這些情況下,引擎可以重建必要的資訊,而不會產生任何額外負擔(這就是零成本的原因)。
自訂例外狀況的堆疊追蹤收集 #
用於內建錯誤的堆疊追蹤機制是使用一般堆疊追蹤收集 API 實作的,使用者腳本也可以使用此 API。函式
Error.captureStackTrace(error, constructorOpt)
會將堆疊屬性新增到指定的 error
物件,在呼叫 captureStackTrace
時產生堆疊追蹤。透過 Error.captureStackTrace
收集的堆疊追蹤會立即收集、格式化並附加到指定的 error
物件。
可選擇的 constructorOpt
參數允許您傳入函式值。收集堆疊追蹤時,會略過此函式最上層呼叫以上的所有框架,包括該呼叫。這有助於隱藏對使用者無用的實作細節。定義會擷取堆疊追蹤的自訂錯誤的常見方式如下:
function MyError() {
Error.captureStackTrace(this, MyError);
// Any other initialization goes here.
}
傳入 MyError 作為第二個引數表示不會在堆疊追蹤中顯示對 MyError 的建構函式呼叫。
自訂堆疊追蹤 #
與 Java 不同,Java 中的例外狀況堆疊追蹤是允許檢查堆疊狀態的結構化值,V8 中的堆疊屬性只會包含格式化堆疊追蹤的平面字串。這純粹只是為了與其他瀏覽器相容。不過,這並非硬編碼,而只是預設行為,使用者腳本可以覆寫它。
為了提高效率,堆疊追蹤並非在擷取時格式化,而是在第一次存取堆疊屬性時才依需求格式化。堆疊追蹤是透過呼叫
Error.prepareStackTrace(error, structuredStackTrace)
來格式化,並使用此呼叫傳回的內容作為 stack
屬性的值。如果您將不同的函式值指定給 Error.prepareStackTrace
,就會使用該函式來格式化堆疊追蹤。它會傳入要為其準備堆疊追蹤的錯誤物件,以及堆疊的結構化表示。使用者堆疊追蹤格式化程式可以自由地格式化堆疊追蹤,甚至傳回非字串值。在 prepareStackTrace
完成呼叫後,保留對結構化堆疊追蹤物件的參照是安全的,因此它也是有效的傳回值。請注意,自訂 prepareStackTrace
函式只會在存取 Error
物件的堆疊屬性後才會呼叫。
結構化堆疊追蹤是一個 CallSite
物件陣列,每個物件都代表一個堆疊框架。CallSite
物件定義下列方法
getThis
:傳回this
的值getTypeName
:傳回this
的類型,為字串。這是儲存在this
的建構函式欄位中的函式名稱(如果有的話),否則為物件的[[Class]]
內部屬性。getFunction
:傳回目前的函式getFunctionName
:傳回目前函式的名稱,通常是其name
屬性。如果沒有name
屬性,則會嘗試從函式的內容推斷名稱。getMethodName
:傳回this
或其原型之一的屬性名稱,該屬性包含目前的函式getFileName
:如果這個函式是在腳本中定義的,則傳回腳本名稱getLineNumber
:如果這個函式是在腳本中定義的,則傳回目前的程式碼行號getColumnNumber
:如果這個函式是在腳本中定義的,則傳回目前的欄位號碼getEvalOrigin
:如果這個函式是使用呼叫eval
建立的,則傳回一個字串,代表呼叫eval
的位置isToplevel
:這是頂層呼叫,也就是說,這是全域物件嗎?isEval
:這個呼叫發生在由呼叫eval
定義的程式碼中嗎?isNative
:這個呼叫是在原生 V8 程式碼中嗎?isConstructor
:這是建構函式呼叫嗎?isAsync
:這是非同步呼叫嗎?(例如await
、Promise.all()
或Promise.any()
)?isPromiseAll
:這是對Promise.all()
的非同步呼叫嗎?getPromiseIndex
:傳回在Promise.all()
或Promise.any()
中所遵循的 Promise 元素的索引,用於非同步堆疊追蹤,或者如果CallSite
不是非同步Promise.all()
或Promise.any()
呼叫,則傳回null
。
預設堆疊追蹤是使用 CallSite API 建立的,因此在那裡可用的任何資訊也都可以透過這個 API 取得。
為了維護嚴格模式函數所施加的限制,具有嚴格模式函數的框架及其下方的所有框架(其呼叫者等)均無法存取其接收器和函數物件。對於這些框架,getFunction()
和 getThis()
會傳回 undefined
。
相容性 #
此處描述的 API 僅限於 V8,且不受任何其他 JavaScript 實作支援。大多數實作確實提供 error.stack
屬性,但堆疊追蹤的格式可能與此處描述的格式不同。建議使用此 API 的方式為
- 僅當您知道自己的程式碼在 v8 中執行時,才依賴格式化堆疊追蹤的配置。
- 不論哪個實作正在執行您的程式碼,設定
Error.stackTraceLimit
和Error.prepareStackTrace
都是安全的,但請注意,只有在您的程式碼在 V8 中執行時,才會產生效果。
附錄:堆疊追蹤格式 #
V8 使用的預設堆疊追蹤格式可以針對每個堆疊框架提供下列資訊
- 呼叫是否為建構呼叫。
this
值的類型(Type
)。- 所呼叫函數的名稱(
functionName
)。 - this 或其原型之一的屬性名稱,用於存放函數(
methodName
)。 - 來源中的目前位置(
location
)
這些資訊中可能有些無法取得,而且會根據可取得多少資訊來使用不同的堆疊框架格式。如果所有上述資訊都可取得,格式化的堆疊框架會如下所示
at Type.functionName [as methodName] (location)
或者,在建構呼叫的情況下
at new functionName (location)
或者,在非同步呼叫的情況下
at async functionName (location)
如果 functionName
和 methodName
僅有一個可取得,或者兩個都可取得但相同,格式為
at Type.name (location)
如果兩個都無法取得,則使用 <anonymous>
作為名稱。
Type
值是儲存在 this
的建構函數欄位中的函數名稱。在 V8 中,所有建構函數呼叫都會將此屬性設定為建構函數,因此除非在建立物件後主動變更此欄位,否則它會存放建立物件的函數名稱。如果無法取得,則會使用物件的 [[Class]]
屬性。
一個特殊情況是全域物件,其中不會顯示 Type
。在這種情況下,堆疊框架會格式化為
at functionName [as methodName] (location)
位置本身有幾種可能的格式。最常見的是定義目前函數的指令碼中的檔案名稱、行號和欄號
fileName:lineNumber:columnNumber
如果目前函數是使用 eval
建立的,格式為
eval at position
…其中 position
是呼叫 eval
發生時的位置。請注意,這表示如果存在巢狀呼叫 eval
,則位置可以巢狀,例如
eval at Foo.a (eval at Bar.z (myscript.js:10:3))
如果堆疊框架位於 V8 的函式庫中,則位置為
native
…如果不可用,則為
unknown location