進入debugger調試時, this 輸出 undefined的問題,箭頭函數與babel造成的調試不便
引言
問題區分
1.箭頭函數內的 this 和封閉的局部變量一樣
2.箭頭函數內的 this 被babel 打包后重命名了
3.正確獲取this 解決方案
引言
之前用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上回答一個問題的答案,有興趣的同學可以點進去看。再加上國內復刻網站的中文鏈接。
from:https://blog.csdn.net/rudy_zhou/article/details/105278657