场景回顾
import A from 'B'
function exec() {
debugger
}
exec()
此处debugger后,很明显无法log到A,因为webpack会把A变量改名,此时引入一个临时变量来方便调试。
import A from 'B'
const temp = A
function exec() {
debugger
}
exec()
我们以为,现在可以顺利的在devtools里打印出temp的值了,但却出现了一个unreference error的错误。很奇怪,我们为什么不能访问到这个全局变量temp呢?
挖掘
在stackoverflow上,我们找到了一个关于闭包的问题
它大致是说这样一个情况:
function baz() {
var x = "foo";
function bar() {
debugger;
};
bar();
}
baz();
此时是无法log到x变量的
function baz() {
var x = "foo";
function bar() {
x
debugger;
};
bar();
}
baz();
但是此时就可以log到
很奇怪,我们继续寻找解释。终于在一个v8的issue里找到了原因:
大致是说,v8的devtools为了性能,做了一些behind the scenes optimization。这导致devtools只能查找到位于当前函数栈或者context object里的对象。(当然这个context object词是我创造的,不知道它是否有真正的v8对应的学名。)
那么什么是context object呢:
每一个闭包的函数,如果该函数返回了一个引入内部变量的函数时,v8并不在栈上创建该引用变量,而是在一个特定的对象里创建该变量,我把这个对象称作context object。其余的变量(未被返回函数引用的),是直接创建在该函数栈上的,该优化导致这些栈上变量无法在devtools里被访问。
当然,也可以直接把这个变量提取到heap里。至于到底是创建在这个context object里,还是直接在堆里创建,不同浏览器实现有所不同,但是不影响我们解决bug。
其中,eval很特殊,它会把所有的local variable都创建在context object里。
解决
知道了原因后,我们可以很简单的解决掉它:
import A from 'B'
const temp = A
;(() => () => {temp})()
function exec() {
debugger
}
exec()
此时你会发现,temp确实可以访问到了。
当然了,上面也提到过了,eval会干掉这个幕后优化,直接把全部对象放到context object里,你也可以使用
import A from 'B'
const temp = A
function exec() {
eval('debugger')
}
exec()
最后
这个issue很有意思,推荐英文好的同学可以阅读一下
Debugger cannot evaluate stack-allocated local variables from outer scopes