通用约定

此页面描述了 Debugger API 中使用的通用约定,并定义了规范中使用的某些术语。

属性

组成 Debugger 接口的对象的属性,以及该接口创建的属性,遵循一些通用约定。

  • 实例和原型是可扩展的;您可以向其中添加自己的属性和方法。

  • 属性是可配置的。这适用于“自身”和原型属性,以及方法和数据属性。(将这些属性开放以进行重新定义,希望这将使 JavaScript 调试器代码更容易应对错误、错误修复以及随着时间的推移接口的变化。)

  • 方法属性是可写的。

  • 我们更倾向于继承的访问器属性而不是自身的数据属性。两者都使用相同的语法进行读取,但继承的访问器似乎更准确地反映了正在发生的事情。除非另有说明,否则这些属性具有 getter 但没有 setter,因为它们不能有意义地被赋值。

被调试程序的值

The Debugger 接口遵循一些约定,以帮助调试器安全地检查和修改被调试程序的对象和值。原始值在调试器和被调试程序之间自由传递;复制或包装由透明地处理。从被调试程序接收到的对象(包括 DOM 元素等主机对象)在调试器中由 Debugger.Object 实例作为前端,这些实例提供面向反射的方法来检查其引用;请参阅下面的 Debugger.Object

在调试器的对象中,只有 Debugger.Object 实例可以传递给被调试程序:当发生这种情况时,被调试程序会收到 Debugger.Object 的引用,而不是 Debugger.Object 实例本身。

在下面的描述中,“被调试程序的值”表示原始值或 Debugger.Object 实例;它是可能从被调试程序接收到的值,或者可以传递给被调试程序的值。

被调试程序的代码

每个 Debugger 实例维护一组全局对象,这些对象合在一起构成了被调试程序。在被调试程序全局对象的范围内(直接或间接)评估的代码被认为是被调试程序代码。同样地

  • 被调试程序帧是运行被调试程序代码的帧;

  • 被调试程序函数是闭包包含被调试程序全局对象的函数(因此函数的代码是被调试程序代码);

  • 被调试程序环境是一个其最外层封闭环境是被调试程序全局对象的的环境;以及

  • 被调试程序脚本是包含被调试程序代码的脚本。

完成值

The Debugger API 通常需要传达运行某些 JS 代码的结果。例如,假设你得到一个 frame.onPop 回调,告诉你被调试程序中的某个方法刚刚完成。它是否成功返回?它是否抛出异常?它返回了什么?调试器将完成值传递给 onPop 处理程序,以说明发生了什么。

完成值是以下之一

  • { return: value }

    代码正常完成,返回valueValue 是被调试程序的值。

  • { throw: value, stack: stack }

    代码抛出value 作为异常。Value 是被调试程序的值。stack 是表示抛出该值的源位置的 SavedFrame,并且可能缺失。

  • null

    代码被终止,就像被“脚本运行缓慢”提醒终止一样。

生成器和异步函数增加了复杂性:它们可以挂起自身(使用 yieldawait),这会将其帧从栈中移除。稍后,生成器或异步帧可能会返回到栈并继续从中断处运行。生成器挂起自身是否算作“完成”呢?

The Debugger API 认为是。 yieldawait 确实会触发 frame.onPop 处理程序,并传递一个解释为什么帧被挂起的完成值。完成值获得额外的 .yield.await 属性,以将此类完成与正常的 return 区分开来。

{ return: value, yield: true }

其中 value 是迭代器结果对象的被调试程序的值,例如 { value: 1, done: false },用于 yield。

当调用生成器函数时,它首先评估任何默认参数表达式并解构其参数。然后其帧被挂起,并将新的生成器对象返回给调用者。此初始挂起会向任何 onPop 处理程序报告为以下形式的完成值

{ return: generatorObject, yield: true, initial: true }

其中 generatorObject 是返回给调用者的生成器对象的被调试程序的值。

当异步函数等待一个 Promise 时,其挂起会向任何 onPop 处理程序报告为以下形式的完成值

{ return: promise, await: true }

其中 promise 是返回给调用者的 Promise 的被调试程序的值。

异步函数第一次等待、返回或抛出异常时,其结果的 Promise 会返回给调用者。如有任何后续恢复异步调用,则直接从作业队列的事件循环启动,栈上没有调用帧。因此,如果需要,onPop 处理程序可以通过检查 Debugger.Frameolder 属性来区分异步调用的初始挂起(返回 Promise)和任何后续挂起:如果该属性为 null,则该调用直接从事件循环恢复。

异步生成器是异步函数和生成器的组合,可以使用 yieldawait 表达式。异步生成器帧的挂起使用上述任何完成值的组合进行报告。

恢复值

随着被调试程序的运行,The Debugger 接口调用各种调试器提供的处理程序函数来报告被调试程序的行为。其中一些调用可以返回值,指示被调试程序的执行应如何继续;这些称为恢复值。恢复值具有以下形式之一

  • undefined

    被调试程序应正常继续执行。

  • { return: value }

    强制被调试程序的顶部帧立即返回value,就像执行 return 语句一样。Value 必须是被调试程序的值。(大多数处理程序函数都支持此功能,但其描述中另有说明的除外。)请参阅下面的特殊情况列表。

  • { throw: value }

    从当前字节码指令抛出value 作为异常。Value 必须是被调试程序的值。请注意,与完成值不同,恢复值不指定栈。当从处理程序启动异常返回时,将使用当前被调试程序栈。如果处理程序想要避免修改已抛出异常的栈,则应返回 undefined

  • null

    终止被调试程序,就像它被“脚本运行缓慢”对话框取消一样。

在某些地方,JS 语言对 return 语句进行特殊处理或根本不允许使用。因此,有一些特殊情况。

  • 没有花括号的箭头函数不能包含 return 语句,但 { return: value } 仍然有效,并返回指定的值。

    同样地,如果被调试程序的顶部帧根本不在函数中——也就是说,它在 script 标签或 eval 代码中运行顶级代码——那么即使在那种代码中 return 语句是非法的,也会返回value。(在 script 标签的情况下,浏览器会丢弃返回值。)

  • 如果被调试程序位于作为构造函数调用的函数中(即,通过new表达式),则value用作函数体返回的值,而不是new表达式产生的值:如果该值不是对象,则new表达式返回框架的this值。

    类似地,如果该函数是子类的构造函数,则非对象值可能会导致TypeError

  • 从生成器返回模拟return,而不是yield;无法强制被调试程序生成器yield

    生成器执行的方式相当奇怪。当第一次调用生成器函数时,它会被放入堆栈并仅运行几条字节码指令(或者更多,如果生成器函数有任何要计算的默认参数值),然后执行“初始挂起”。此时,会创建一个新的生成器对象并将其返回给调用方。此后,调用方可以通过调用genObj.next()随时使生成器的执行恢复,并且生成器可以使用yield再次暂停自身。

    JS 生成器通常无法在“初始挂起”之前返回——没有地方可以放置return语句——但{ return: value } 在那里仍然有效,它替换了初始挂起通常会创建和返回的生成器对象。

    从通过genobj.next()(或其他方法)恢复的生成器返回会关闭生成器,并且genobj.next()或其他方法会返回一个新对象,其形式为{ done: true, value: value }

如果调试器钩子函数抛出异常,而不是返回恢复值,我们绝不会将此类异常传播到被调试程序;相反,我们调用关联的Debugger实例的uncaughtExceptionHook属性,如下所述。

时间戳

时间戳以自任意但固定的纪元以来的毫秒为单位表示。时间戳的分辨率通常大于毫秒,尽管不保证任何特定分辨率。

Debugger.DebuggeeWouldRun 异常

一些看似仅检查被调试程序状态的调试器操作实际上可能导致被调试程序代码运行。例如,读取变量可能会在全局或with表达式的操作数上运行 getter 函数;获取对象的属性描述符将在对象是代理时运行处理程序陷阱。为了保护调试器的完整性,只有声明目的是运行被调试程序代码的方法才能这样做。这些方法称为调用函数,并且它们遵循某些通用约定以安全地报告被调试程序的行为。对于其他方法,如果其正常操作会导致被调试程序代码运行,则它们会抛出Debugger.DebuggeeWouldRun异常的实例。

如果堆栈上有来自多个 Debugger 实例的调试器框架,则抛出的异常是顶层锁定调试器的全局的Debugger.DebuggeeWouldRun的实例。

Debugger.DebuggeeWouldRun异常可能具有cause属性,提供有关被调试程序为何会运行的更多详细信息。cause属性的值是以下字符串之一

  • "proxy":执行操作会导致代理处理程序运行。|

  • "getter":执行操作会导致对象属性 getter 运行。|

  • "setter":执行操作会导致对象属性 setter 运行。|

如果系统无法确定控制尝试进入被调试程序的原因,它将使异常的cause属性保持未定义。