检测那些错误地或无意中被添加到全局作用域的变量,对于调试应用程序并避免命名冲突非常有帮助。随着一个网页应用及其依赖的增长,理解全局作用域中发生了什么变得愈加重要。例如,确保多个库甚至多个应用可以在页面上共存而不会发生全局命名冲突。
在本文中,我将向你展示如何在网页应用的运行时,找到被添加到全局作用域中的变量)。
需要注意的是,大多数情况下,使用
globalThis
属性会是更好的选择,因为它适用于不同的 JavaScript 环境。不过,本文专注于 Web(非 Worker)上下文,因此使用window
一词会更容易理解。
假设你想查看一个网页中 window
对象上添加了哪些全局变量。
举个例子,以下是一些(故意写得很糟糕的)代码,这些代码向全局作用域添加了多个变量(例如,jQuery
是库本身添加的,i
是因为脚本未使用 "use strict"
而被意外添加的)。
<html>
<body>
<h1>Hello world!</h1>
<script src="https://unpkg.com/jquery@3.6.0/dist/jquery.js"></script>
<script>
function doSomethingTwice() {
for (i = 0; i <= 2; i++) {
const myString = `hello-world-${i}`;
// 假设我们这里会用 myString 做些事情……
}
}
doSomethingTwice();
</script>
</body>
</html>
通常,你可能会打开开发者工具(DevTools)控制台,检查 window
对象来寻找可疑的变量。
这种方法是可行的,但... 代价不小。浏览器和 JavaScript 引擎自身会在 window
对象上添加许多全局变量(例如 JavaScript API,如 localStorage
等),因此要找出我们代码引入的全局变量,无异于大海捞针。
一种可能的解决方法
我们可以先获取所有默认的全局变量列表,然后在 window
对象中过滤掉这些默认变量。以下代码片段可以在开发者工具(DevTools)控制台中运行,实现这一目标:
const browserGlobals = ['window', 'self', 'document', 'name', 'location', 'customElements', 'history', 'locationbar', 'menubar', 'personalbar', 'scrollbars', 'statusbar', 'toolbar', 'status', 'closed', 'frames', 'length', 'top', ...];
const runtimeGlobals = Object.keys(window).filter(key => {
const isFromBrowser = browserGlobals.includes(key);
return !isFromBrowser;
});
console.log("Runtime globals", runtimeGlobals);
尽管这段代码能奏效,但它引出了两个问题:
- 如何获取
browserGlobals
的变量列表? - 考虑到跨浏览器差异和 JavaScript API 的更新,维护
browserGlobals
列表可能会很复杂,有没有更好的办法?
更优解决方案:程序生成 browserGlobals
列表
我们可以通过以下方法,动态生成 browserGlobals
列表:
- 创建一个临时的 iframe 指向
about:blank
(确保window
对象是干净的状态)。 - 检查 iframe 的
window
对象并存储其全局变量名。 - 移除 iframe。
以下代码实现了上述过程:
(function () {
// 获取浏览器默认的全局变量
const iframe = window.document.createElement("iframe");
iframe.src = "about:blank";
window.document.body.appendChild(iframe);
const browserGlobals = Object.keys(iframe.contentWindow);
window.document.body.removeChild(iframe);
// 过滤出运行时添加的全局变量
const runtimeGlobals = Object.keys(window).filter((key) => {
const isFromBrowser = browserGlobals.includes(key);
return !isFromBrowser;
});
console.log("Runtime globals", runtimeGlobals);
})();
运行上述代码后,你将会得到一个干净的运行时全局变量列表。👍
更复杂的版本:RuntimeGlobalsChecker
工具
以下是一个更复杂的实现版本,可以方便地复用:
window.__runtimeGlobalsChecker__ = (function createGlobalsChecker() {
let browserGlobals = [];
const ignoredGlobals = ["__runtimeGlobalsChecker__"];
function collectBrowserGlobals() {
const iframe = window.document.createElement("iframe");
iframe.src = "about:blank";
window.document.body.appendChild(iframe);
browserGlobals = Object.keys(iframe.contentWindow);
window.document.body.removeChild(iframe);
return browserGlobals;
}
function getRuntimeGlobals() {
if (browserGlobals.length === 0) {
collectBrowserGlobals();
}
return Object.keys(window).filter((key) => {
const isFromBrowser = browserGlobals.includes(key);
const isIgnored = ignoredGlobals.includes(key);
return !isFromBrowser && !isIgnored;
});
}
return {
getRuntimeGlobals,
};
})();
使用说明:
- 此工具会作为单例对象
__runtimeGlobalsChecker__
附加到window
对象。 - 可随时通过调用
window.__runtimeGlobalsChecker__.getRuntimeGlobals()
检查运行时全局变量。
注意事项
-
在干净的浏览器环境中运行:避免扩展程序(如 React DevTools)向
window
对象注入全局变量,导致结果混杂。 - 在 CI 环境中运行:该工具可以集成到持续集成(CI)流程中,例如在 E2E 测试中使用(如 Cypress)进行自动反馈。
- 保存代码片段:避免每次在 DevTools 控制台重复粘贴代码,可以创建一个 JavaScript 片段(snippet)方便调用。