第一个代码片段:
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
,然后顶级称为hello
。hello
是在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
返回函数b
或42
数并不重要。b
记得val
这一事实同样无关紧要。
执行堆栈唯一关心的是,当执行函数b
时,哪个函数的执行导致了它。在第二个示例中,hello
是由main
创建的结果,但其执行是由 top 而不是main
引起的。