同事出的考題,滿是複雜 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 在宣告屬性時可設定 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 被某個內嵌網頁改掉的。

但若修改來源來自 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,立馬收下跪拜(跪)

Post a comment