JavaScript 進階偵錯 - 抓出誰偷改變數?
4 |
同事出的考題,滿是複雜 JavaScript 與 HTML 元素的古蹟網頁,有個全域變數固定在某個時間點被不明來源改成 undefined。(註:透過全域變數溝通非良好設計,但既為古蹟,一磚一瓦都有故事,還是盡量維持原貌吧。)
我用以下簡化範例重現問題:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
var theFlag = false;
//... complex legacy code ...
let count = 5;
let hnd = setInterval(function() {
console.log(`${count} theFlag = ${theFlag}`);
if (count-- <= 1) clearInterval(hnd);
}, 1000)
</script>
<div>
<!-- ... complex legacy HTML ... -->
<!-- ... more complex legacy HTML ... -->
</div>
</body>
</html>
這種情況,除了地毯式搜尋盤查所有可能的相關程式,有沒有更有效率的做法?例如:設法在變數被修改時觸發中斷,再由 Call Stack 查異動來源。
JavaScript 在宣告屬性時可設定 getter 與 setter,跟 C# 一樣可為屬性自訂讀取跟寫入邏輯。但 theFlag 是個變數不能加工怎麼辦?我找到有個神奇的 Object.defineProperty() 可為物件新增屬性,window 是個物件,為其新增名為 theFlag 屬性取代 theFlag 變數,寫法一樣是 theFlag = xxx。在 theFlag setter 函式我們加上 console.log() 列印偵錯訊息並加上 debugger 觸發中斷,便能在 theFlag 被修改的當下從 Call Stack 追查兇手。
<script>
var _x = false;
Object.defineProperty(window, 'theFlag', {
get: function() { return _x; },
set: function(v) {
console.log(`theFlag is set to ${v}`);
_x = v;
debugger;
}
});
//var theFlag = false;
//... complex legacy code ...
let count = 5;
let hnd = setInterval(function() {
console.log(`${count} theFlag = ${theFlag}`);
if (count-- <= 1) clearInterval(hnd);
}, 1000)
</script>
靠著這招,中斷時由 Call Stack (呼叫堆疊) 反查很快查出 theFlag 被某個內嵌網頁改掉的。
但若修改來源來自 window.open() 另開的網頁,則不會出現在 Call Stack,活像天外飛來一筆,看不出是誰改的:
要克服這個問題,我找到 arguments.callee.caller.toString() 可查看呼叫來源函式:
var _x = false;
Object.defineProperty(window, 'theFlag', {
get: function() { return _x; },
set: function(v) {
console.log(`theFlag is set to ${v} by`);
console.log(arguments.callee.caller.toString());
_x = v;
}
});
//var theFlag = false;
雖不像 Call Stack 能跳到原始碼所在位置,但至少取得更明確的資訊,而在實際案例中,我便靠著這條線索找到了兇手。
昨天分享 StackOverflow 開發者調查心得後,有朋友說:為什麼 JavaScript 的好感度還超過 60%? (尖叫)
說實在的,JavaScript 的靈活彈性真讓人又愛又恨,哈!
[2022-06-25 更新] 感謝讀者陳威任分享好用小技巧一則,使用 console.trace() 可在主控台輸出 Stack Trace,且能涵蓋 window.open() 另開視窗的程式來源,好用! 收入工具箱。
A case that using JavaScript property setter find out who alter the global variable.
Comments
# by 布丁布丁吃布丁
真的是鍵盤柯南(?
# by 睿
感謝分享實用技巧👍
# by Ian
這個技巧真的蠻實用的哈哈~~
# by 樂透無名
昨天才追蹤JS,立馬收下跪拜(跪)