进入debugger调试时, this 输出 undefined的问题,箭头函数与babel造成的调试不便
引言
之前用VUE
开发的时候经常遇到,用 chrome 的调试工具进入页面 debugger
的时候,用 console.log(this)
能输出 this
的值。但是在断点过程中,用鼠标移动到 this
上显示的确是 undefined
(在控制台中输出 this
也是 undefined
)。说实话,当时是因为影响并不大,也没在意,也没探究过具体的原因。昨天刚好手上任务完成,就抽了一些时间去仔细找找具体的原因以及解决方案。
问题区分
对的,你没看错,这个问题要区分一下,因为这个问题并不只是一个问题。这里涉及到多个问题,我在查找原因的时候就发现有人问类似的问题。当我知道具体原因后就发现,问的以及回答的存在牛头不对马嘴的情况。
1.箭头函数内的 this 和封闭的局部变量一样
这里不展开分享箭头函数,主要讲一点,箭头函数里的 this
跟封闭的局部变量一样,如果箭头函数内部未显式的写出 this
,进入这个箭头函数内部的断点,this
输出的是 undefined
,看下面这个例子你就知道了。
这个动图写了两个例子,一个箭头函数内只写了一个debugger
,另一个还显式的写了this
,都进入断点时,第一个输出undefined
,第二个输出了Window
对象。这就是进入断点在控制台中输出this
为 undefined
的第一个问题。
至于出现原因就是因为chrome
调试器的优化,如果未在函数内部引用局部变量(这里是this
),这个变量就不会存储在此函数上下文对象中。所以总结就是箭头函数内部的this
(这里不谈指向),生存周期与普通函数的封闭局部变量一样,都是未显式引用输出就是undefined
(针对chrome 调试,火狐不会)。
有兴趣的小伙伴可以进入这篇 Chrome调试器为何认为封闭的局部变量未定义?中看看其他牛人的讨论,如果英语足够好也可以进原英文链接 Why does Chrome debugger think closed local variable is undefined? 相信这里能完全解决你此问题的疑惑。
2.箭头函数内的 this 被babel 打包后重命名了
刚了解到这个问题的时候就去babel
官网看了,找到 Why is this being remapped to undefined? 这样一个问题,我兴奋的以为,我找到了答案。但被事实狠狠打大了一把脸。这里问的主要是因为 babel
ES2015模块是隐式严格模式的,所以即使是上方第一个问题用普通函数输出也是undefined
(严格模式下用window.
调用函数,函数内部this
才会指向 Window
对象)。
回到我们的具体问题。进入断点时 console.log(this)
输出了内容,而直接在控制台写 this
执行或者鼠标移到断点处的 this
上显示 undefined
是什么原因(这里不是探究为什么显示undefined
了,而是为什么和代码中console.log(this)
输出的不一致,即使解决了输出undefined
,也就是移除严格模式,这里的this
应该也只是输出 Window
对象,而不是我们当前运行环境中的比如Vue
这个组件对象)。
因为在项目中使用了babel
。比如箭头函数就会被打包成普通函数,而this
指向就会用变量保存来代替,比如_this
,_this2
之类的。
我把代码例子贴出来大家就知道了,我用的vue
就用vue
使用的一个箭头函数的例子解释。
/* 这个代码是vue methods 钩子下的一个函数,是我的源代码。*/
handle() {
this.add().then(() => {
console.log(this.number);
debugger;
});
}
/* 这个代码就是上方代码在项目运行中,打包后的代码 */
handle: function handle() {
var _this2 = this;
this.add().then(function () {
console.log(_this2.number);
debugger;
});
}
下面的截图就是在运行中Sources 下进入断点的代码
从上面明显可以看到,这里的this
已经在babel
打包后赋值给了_this2
这个变量。意思就是虽然我们断点进入的是比如上方的About.vue
这个文件,实际运行的代码是左侧这个cjs.js?
这个文件。这种运行环境下你能看到 Console
下 直接写this
输出是undefined
,而在About.vue
这个文件中console.log(this.number)
实际是cjs.js
这个文件中的 console.log(_this2.number)
输出的。
这里为什么进入断点时在.vue
文件中,实际是在.js
文件呢,是因为vue
配置webpack
的 源映射 source-map 的默认配置。默认配置在打包速度上稍慢,但是胜在调试更加方便。也可以改成其他配置,点击上面的链接可以进入官网查看详细配置,这里就不谈了。
.vue
就是断点这里this
没有指向值,如果想调试查看你想要的 this
值,可以在cjs.js
这个文件里看,不过因为打包后和实际写的源代码有较多差异你也可以在Watch
下添加_this2
(为什么是_this2
,接着看完吧) 监听,比如下面的例子。
这里因为我测试的例子很简单,所以这里this
是用变量_this2
保存的。babel
都是用_this
开头的变量保存的 this
,所以大家可以在自己项目中多尝试一下,因为这个具体赋值到this?
上根据项目代码场景确定的。
也可以像我这样,进入断点时在控制台输入 _this
这里提示我 是 _this6
,如果实在不找不到就接着看下面。
3.正确获取this 解决方案
说到底难道没有不添加Watch
的办法吗,而且这里还是不能把鼠标移动到this
上提示预期值,其实也是有一些比较婉转的解决方案的。
第一个,如果项目不用向下兼容,那么推荐不要使用babel
了,嘿嘿,这个简单粗暴。(以下动图演示能看到这里的运行代码就没被babel
打包,因为我把babel
移除了)
但是,既然你能遇到这个问题,肯定是项目中需要使用babel
的,那么我们用一个插件来解决一下。
npm i babel-plugin-transform-es2015-arrow-functions --save-dev
然后在.babelrc
或者是babel.config.js
配置文件中加入
plugins: [["transform-es2015-arrow-functions", { spec: true }]]
运行你的代码,进入断点就会发现。
项目确实被babel
打包了,但是箭头函数编译方式跟之前不一样了,之前是使用变量保存的方式,现在是使用bind
的方式。也就是内部函数this
的值被更改为外部函数this
值了。这样就可以直接在断点处查看this
的期望值,以后调试前端代码也能更加方便。虽然此方法获取来源的提供者说并非在所有的地方都行之有效,但经测试,我在最新构建的Vue
项目中以及以前老的项目中都能使用。如果有遇到不能使用的情况,欢迎反馈哈。
此方法是参考 loganfsmyth 在Stack Overflow上回答一个问题的答案,有兴趣的同学可以点进去看。再加上国内复刻网站的中文链接。
以上解释如果描述不当或欠缺,欢迎指正,谢谢。