console.trace() 方法在同步和异步闭合函数调用中给出不同的输出



第一个代码片段:

console.log("First")
function main() { 
var n = 10;

function nested(argument) {
console.log(n);
console.trace();
}
setTimeout(nested, 0);
console.trace();
console.log("Last command inside nested function");   
}
main();
console.trace();
console.log("Last");

第一个代码片段的输出

在这里,nested在记录Last后被异步调用。

console.trace()命令显示所有函数都在执行堆栈中,包括声明nested的函数main

我猜这是因为即使main的执行上下文已从执行堆栈中弹出,nested仍然可以访问其词法环境,这就是它显示所有功能的原因。

第二个代码片段:

console.log("First")
function main() { 
var n = 10;

function nested(argument) {
console.log(n);
console.trace();
}
console.trace();
console.log("Last command inside main function");   
return nested;
}
var hello = main();
hello();
console.log("Last");

第二个代码片段的输出

在这种情况下,nested在从函数main返回后被同步调用。 在这种情况下,只有nested和全局匿名函数显示在执行堆栈中。

为什么?第二个不应该也显示执行堆栈中的所有函数吗?

console.trace() 命令显示所有函数都在执行堆栈中

他们不是。

浏览器的开发工具确实会跟踪初始调用堆栈,然后在调用console.trace()或获取Error实例的.stack时重建它以方便您。但是初始函数不再在堆栈上.
并非所有浏览器都保留该异步跟踪,例如 Safari 不会,并且在您的第一个示例中,跟踪将从nested开始。这实际上是正确的,但对于调试不是很有用.
您可以在跟踪中看到,您的浏览器(Chrome)确实通过用(async)后缀标记上一个条目来显式标记"真实"堆栈结束的位置。Firefox在他们的痕迹中也有类似的标记。

当你开始处理async函数时,它会变得更加令人兴奋,因为在引擎遇到await子句之后,之前发生的一切,即使它在技术上属于同一个函数,也不再在堆栈上。再一次,如果可以的话,您可能需要检查Safari如何报告它,因为它们实际上确实公开了内部方法:

async function foo() {
await null;
console.trace();
}
foo();

输出

Trace
(anonymous function) (js:3)
asyncFunctionResume
(anonymous function)
promiseReactionJobWithoutPromise

可以看到foo和全球代码都丢失了。

如果您愿意,可以在这篇 V8-dev 文章中阅读有关此异步跟踪功能的更多信息:https://docs.google.com/document/d/13Sy_kBIJGP0XT34V1CV3nkWya4TwYx9L3Yv45LdGB6Q/edit

我猜这是因为即使main的执行上下文已从执行堆栈中弹出,nested仍然可以访问其词法环境,这就是它显示所有功能的原因。

正如你现在可能更好地理解的那样,这不是原因,不。之所以显示它,是因为您的浏览器认为,如果您需要跟踪,则更多的是因为您想知道调用来自代码中的位置,而不是内部调用堆栈上的实际内容。毕竟,目标是对开发人员有用。

在这种情况下,只有nested和全局匿名函数显示在执行堆栈中。

因为它们只是保留在调用堆栈上的内容。对nested的调用是从<script>标签执行创建的全局(匿名)上下文中进行的,main已经执行完成并从堆栈中删除,一切都是应有的。

第二个不应该也显示执行堆栈中的所有函数吗?

不。在第一个示例中,如跟踪所示,顶级称为main,它调用setTimeout,这导致了nested的执行。

在第二个示例中,顶级称为main,然后顶级称为hellohello是在main内部构造的(并且它关闭了某些变量)这一事实与执行堆栈无关。

原因与同步或异步无关:

function a(val) {
function b() {
console.trace(val);
}
b();
}
const c = a(42);  // toplevel calls a, a calls b

function a(val) {
return function b() {
console.trace(val);
}
}
const c = a(42);  // toplevel calls a
c();              // toplevel calls b (a.k.a. c)

在这两种情况下,b都会关闭val,并且在这两种情况下都没有异步性,但结果与您的示例相似。这是因为堆栈不关心在哪里定义你的值。就执行栈而言,a返回函数b42数并不重要。b记得val这一事实同样无关紧要。

执行堆栈唯一关心的是,当执行函数b时,哪个函数的执行导致了它。在第二个示例中,hello是由main创建的结果,但其执行是由 top 而不是main引起的。

最新更新