在現代 Web 開發中,我們經常會引入第三方腳本——廣告、統計、SDK、插件等。這些腳本爲了“方便”,常常直接向全局作用域(window 對象)注入變量、函數甚至修改原生對象。這種行爲不僅會污染全局命名空間,還可能導致變量衝突、性能下降、安全風險,甚至悄無聲息地篡改關鍵 API(比如重寫 console.log 或 XMLHttpRequest)。
那麼,作爲開發者,我們如何準確識別一個第三方腳本到底往 window 上加了什麼、改了什麼?本文將帶你手把手實現一個輕量級但可靠的監控方案。
一、問題場景
假設我們要加載一個廣告腳本:
<script src="https://cdn.wwads.cn/js/makemoney.js"></script>
我們想知道:
- 它新增了哪些全局變量?
- 它是否修改了已有的全局屬性(比如
Array、Promise或自定義變量)?
僅靠 Object.keys(window).length 只能知道數量變化,無法定位具體內容。我們需要更精細的對比。
二、核心思路:快照 + 差異對比
要檢測“變化”,最直接的方法是:
- 加載腳本前,對
window做一次完整快照(屬性名 + 值)。 - 加載腳本後,再做一次快照。
- 對比兩次快照,找出新增項和值發生變化的項。
關鍵點:
- 使用
Object.getOwnPropertyNames(window)獲取所有屬性(包括不可枚舉的)。 - 使用
Map存儲屬性名與值的映射,便於高效查找。 - 使用
Object.is()進行值比較,避免NaN、-0/+0等邊界問題。 - 包裹
try...catch,防止訪問某些特殊屬性時拋出異常。
三、完整實現代碼
// 1. 創建 window 快照:記錄所有屬性及其當前值
function snapshotWindow() {
const props = Object.getOwnPropertyNames(window);
const snap = new Map();
for (const key of props) {
try {
snap.set(key, window[key]);
} catch (e) {
// 某些屬性(如跨域 iframe 中的 opener)訪問會報錯
snap.set(key, '<ACCESS_ERROR>');
}
}
return snap;
}
// 2. 記錄加載前的狀態
const beforeSnapshot = snapshotWindow();
// 3. 動態加載第三方腳本
const script = document.createElement('script');
script.src = 'https://cdn.wwads.cn/js/makemoney.js';
// 4. 腳本加載完成後進行對比
script.onload = () => {
// 稍作延遲,確保腳本完全執行(含微任務)
setTimeout(() => {
const afterSnapshot = snapshotWindow();
const added = [];
const modified = [];
// 遍歷加載後的所有屬性
for (const [key, newValue] of afterSnapshot) {
if (!beforeSnapshot.has(key)) {
added.push(key); // 新增
} else {
const oldValue = beforeSnapshot.get(key);
if (!Object.is(oldValue, newValue)) {
modified.push({ key, oldValue, newValue }); // 被修改
}
}
}
console.log('🟢 新增的全局變量:', added);
console.log('🟡 被修改的全局變量:', modified);
}, 100);
};
script.onerror = () => console.error('第三方腳本加載失敗');
document.head.appendChild(script);
四、輸出示例與解讀
運行上述代碼後,控制檯可能輸出:
🟢 新增的全局變量: ['_AdBlockInit', '_IsTrustedClick']
🟡 被修改的全局變量: [
{ key: "isMobile", oldValue: [Function], newValue: [Object] }
]
這意味着:
- 腳本注入了
_AdBlockInit等廣告相關變量; - 它可能重寫了
isMobile對象。
這類行爲在廣告或數據採集腳本中並不罕見,但對應用穩定性和安全性構成潛在威脅。
五、注意事項與進階建議
1. 動態屬性誤報
某些 window 屬性是 getter(如 window.devicePixelRatio),每次讀取值可能不同。這類屬性可能被誤判爲“修改”。可建立白名單過濾:
const DYNAMIC_PROPS = new Set(['devicePixelRatio', 'innerWidth', 'innerHeight', 'scrollY']);
if (DYNAMIC_PROPS.has(key)) return; // 跳過對比
2. 深度修改無法檢測
如果腳本只修改了某對象的內部屬性(如 window.myLib.config = {...}),而 myLib 引用未變,則不會被識別。如需深度監控,可結合 Proxy 或定期遍歷,但代價較高。
3. 生產環境慎用
此方案主要用於開發調試或安全審計,不建議在生產環境長期運行,因其會遍歷整個 window,有一定性能開銷。
4. 結合 CSP 與沙箱
對於高安全要求的場景,建議:
- 使用 Content Security Policy (CSP) 限制腳本來源;
- 將第三方腳本運行在 iframe 沙箱 中,徹底隔離全局作用域。
5. 隔離方案:運行後自動恢復關鍵全局變量
如果你已知某些第三方腳本會覆蓋特定全局變量(如 isMobile 或 config),但又無法避免加載該腳本,可以採用“備份-恢復”策略,在腳本執行後自動還原關鍵變量,避免副作用擴散:
function loadThirdPartyScript(src, backupGlobals = []) {
const backup = {};
backupGlobals.forEach(key => {
backup[key] = window[key];
});
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = script.onerror = () => {
backupGlobals.forEach(key => {
window[key] = backup[key];
});
};
document.head.appendChild(script);
}
// 使用示例
loadThirdPartyScript('https://cdn.wwads.cn/js/makemoney.js ', ['isMobile', 'config']);
⚠️ 注意:此方法僅適用於腳本同步執行完畢後立即產生副作用的場景。若腳本通過
setTimeout、事件監聽器或異步回調持續修改全局變量,則恢復操作可能失效。
六、總結
第三方腳本如同“黑盒”,我們無法控制其實現,但可以主動監控其副作用。通過簡單的快照對比,就能清晰看到它對全局環境的“入侵”行爲,爲性能優化、安全審計和故障排查提供有力依據。
下次當你引入一個未知腳本時,不妨先問一句:“你到底往 window 上寫了什麼?”
💡 小技巧:將上述邏輯封裝爲一個函數
detectGlobalPollution(scriptUrl),可複用於任何第三方腳本的檢測。


加載中...