检测错误或无意中添加到全局作用域的变量有助于调试应用程序并避免命名冲突。随着 web 应用程序及其依赖项的增长,了解全局作用域中的情况变得越来越重要(例如,确保多个库甚至多个应用程序可以共存于同一页面上,而不会发生全局命名冲突)。
在这篇文章中,将向你展示如何在运行时查找 web 应用中添加到全局作用域的变量
请注意,在大多数情况下,globalThis
属性是访问全局对象的更好选择,因为它在不同的 JavaScript 环境中都适用。但本文特定于 web(非 worker)上下文,因此使用 window
术语会更容易理解。
假设你想检查某个网页上哪些全局变量被添加到 window
对象中。以下(故意写得很糟糕的)代码就是一个例子,它向全局作用域添加了多个变量(例如,jQuery 是由库本身添加的,i
是因为脚本没有使用 "use strict"
而被添加的)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <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}`; } } doSomethingTwice(); </script> </body> </html>
|
通常,你可能会打开开发者工具的控制台并检查 window
对象,寻找可疑的变量。
这种方法可行,但工作量很大。浏览器和 JavaScript 引擎本身会在 window
对象上添加大量全局变量(例如 JavaScript API,如 localStorage
等),因此查找我们代码引入的全局变量就像大海捞针。
一个可行的解决方案是获取所有默认的全局变量,并通过运行类似的代码片段,从 window
对象中过滤它们:
1 2 3 4 5 6 7 8
| 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
列表,将其填充为原始 window
对象的全局变量。
有几种方法可以实现,但对我而言,最简洁的方法是:
- 创建一个指向
about:blank
的临时 iframe,以确保 window
对象处于干净状态。
- 检查 iframe 的
window
对象并存储其全局变量名称。
- 移除 iframe。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| (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); })();
|
在控制台中运行上述代码片段,你最终会看到一个干净的运行时变量列表👍
更复杂的版本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
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(); } const runtimeGlobals = Object.keys(window).filter((key) => { const isFromBrowser = browserGlobals.includes(key); const isIgnored = ignoredGlobals.includes(key); return !isFromBrowser && !isIgnored; }); return runtimeGlobals; }
return { getRuntimeGlobals, }; })();
|
最后几点建议:
- 该工具可以轻松地在持续集成环境中运行(例如在使用 Cypress 的 E2E 测试中)以提供自动化反馈。
- 我建议在没有扩展的浏览器标签页中运行此工具:大多数浏览器扩展会向
window
对象注入全局变量,增加结果中的噪声(例如,React DevTools 扩展会添加 __REACT_DEVTOOLS_BROWSER_THEME__
等变量)。
- 为了避免在开发者工具控制台中重复粘贴全局检查器代码,你可以创建一个 JavaScript 代码片段。