2

JavaScript 進階偵錯 - 抓出誰偷改變數?

 1 year ago
source link: https://blog.darkthread.net/blog/trace-js-var-change-source/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

JavaScript 進階偵錯

同事出的考題,滿是複雜 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>

Fig1_637916732661021783.png

這種情況,除了地毯式搜尋盤查所有可能的相關程式,有沒有更有效率的做法?例如:設法在變數被修改時觸發中斷,再由 Call Stack 查異動來源。

JavaScript 在宣告屬性時可設定 gettersetter,跟 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 被某個內嵌網頁改掉的。

Fig2_637916732661701132.png

但若修改來源來自 window.open() 另開的網頁,則不會出現在 Call Stack,活像天外飛來一筆,看不出是誰改的:

Fig4_637916732662209058.png

要克服這個問題,我找到 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 能跳到原始碼所在位置,但至少取得更明確的資訊,而在實際案例中,我便靠著這條線索找到了兇手。

Fig3_637916732662826946.png

昨天分享 StackOverflow 開發者調查心得後,有朋友說:為什麼 JavaScript 的好感度還超過 60%? (尖叫)

說實在的,JavaScript 的靈活彈性真讓人又愛又恨,哈!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK