您是否知道带有 ID 的 DOM 元素可以在 JavaScript 中作为全局变量访问? 这是那些一直存在的东西之一,就像,永远,但我真的是第一次深入研究它。
如果这是您第一次听说它,请做好准备! 我们可以简单地通过向 HTML 中的元素添加一个 ID 来查看它的实际效果:
通常,我们会使用定义一个新变量 querySelector("#cool")
or getElementById("cool")
选择该元素:
var el = querySelector("#cool");
但我们实际上已经可以访问 #cool
没有那种拘谨:
所以,任何 id
- 要么 name
属性,就此而言——在 HTML 中可以使用 JavaScript 访问 window[ELEMENT_ID]
. 同样,这并不完全是“新的”,但确实很少见。
正如您可能猜到的那样,使用命名引用访问全局范围并不是最好的主意。 有些人开始称其为“全球范围污染者”。 我们将探讨为什么会这样,但首先……
一些背景
这种方法是 HTML 规范中概述的,它被描述为“在 Window
目的。”
Internet Explorer 是第一个实现该功能的。 所有其他浏览器也添加了它。 Gecko 是当时唯一不直接在标准模式下支持它的浏览器,而是选择将其作为实验性功能。 完全有实施它的犹豫,但它 以浏览器兼容性的名义前进 (壁虎甚至试图 说服 WebKit 将其移出标准模式)并最终在 Firefox 14 中进入标准模式。
可能不为人所知的一件事是,浏览器必须采取一些预防措施——取得了不同程度的成功——以确保生成的全局变量不会破坏网页。 其中一项措施是……
可变阴影
这个特性最有趣的部分可能是命名元素引用不 隐藏现有的全局变量. 所以,如果一个 DOM 元素有一个 id
已经定义为全局的,它不会覆盖现有的。 例如:
window.foo = "bar";
I won't override window.foo
console.log(window.foo); // Prints "bar"
反之亦然:
I will be overridden :(
window.foo = "bar";
console.log(window.foo); // Prints "bar"
这种行为是必不可少的,因为它消除了危险的覆盖,例如
,否则会通过使 alert
API。 这种保护技术很可能是你——如果你和我一样——第一次了解这个的原因。
针对命名全局变量的案例
早些时候,我说过使用全局命名元素作为引用可能不是最好的主意。 有很多原因,其中 TJ VanToll 在他的博客上进行了很好的介绍 我将在这里总结:
- 如果 DOM 发生变化,那么引用也会发生变化。 这使得一些非常“脆弱”(规范的术语 对于它)代码,其中 HTML 和 JavaScript 之间的关注点分离可能太多。
- 意外引用太容易了。 一个简单的错字很可能最终会引用一个命名的全局并给你意想不到的结果。
- 它在浏览器中的实现方式不同。 例如,我们应该能够使用
id
—例如— 但有些浏览器(即 Safari 和 Firefox)返回
ReferenceError
在控制台中。 - 它可能不会返回您的想法。 根据规范,当 DOM 中有同一个命名元素的多个实例时——比如说,两个
— 浏览器应该返回一个
HTMLCollection
带有实例数组。 然而,Firefox 只返回第一个实例。 再说一次, 规范说 我们应该使用一个实例id
无论如何,在元素的树中。 但这样做不会阻止页面工作或类似的事情。 - 也许有性能成本? 我的意思是,浏览器必须列出并维护它。 几个人跑了测试 在这个 StackOverflow 线程中,其中命名的全局变量实际上是 在一项测试中表现更好 和 在最近的测试中表现较差.
其他注意事项
假设我们放弃了对使用命名全局变量的批评,并且无论如何都要使用它们。 都很好。 但是,您可能需要考虑一些事情。
填充物
尽管听起来很极端,但这些类型的全局检查是 polyfill 的典型设置要求。 查看以下示例,其中我们使用新设置的 cookie CookieStore
API,在还不支持它的浏览器上填充它:
// Polyfill the CookieStore API if not yet implemented.
// https://developer.mozilla.org/en-US/docs/Web/API/CookieStore
if (!window.cookieStore) {
window.cookieStore = myCookieStorePolyfill;
}
cookieStore.set("foo", "bar");
此代码在 Chrome 中运行良好,但在 Safari 中引发以下错误:
TypeError: cookieStore.set is not a function
Safari 不支持 CookieStore
撰写本文时的 API。 结果,没有应用 polyfill,因为 img
元素 ID 创建一个与 cookieStore
全球性的。
JavaScript API 更新
我们可以反过来发现另一个问题,即更新浏览器的 JavaScript 引擎会破坏命名元素的全局引用。
例如:
window.BarcodeDetector.focus();
该脚本获取对输入元素的引用并调用 focus()
在上面。 它工作正常。 仍然,我们不知道如何 长 它将继续工作。
你看,我们用来引用输入元素的全局变量将在浏览器开始支持 BarcodeDetector
API. 那时, window.BarcodeDetector
global 将不再是对输入元素的引用,并且 .focus()
会抛出一个“window.BarcodeDetector.focus
不是函数”错误。
奖励:并非所有命名元素都会生成全局引用
想听一些有趣的事情吗? 雪上加霜的是,仅当名称仅包含字母时,命名元素才能作为全局变量访问。 浏览器不会为 ID 包含特殊字符和数字的元素创建全局引用,例如 hello-world
和 item1
.
结论
让我们总结一下我们是如何到达这里的:
- 所有主流浏览器都会自动创建对每个 DOM 元素的全局引用,并使用
id
(或者,在某些情况下,一个name
属性)。 - 通过它们的全局引用访问这些元素是不可靠的并且有潜在的危险。 利用
querySelector
orgetElementById
代替。 - 由于全局引用是自动生成的,它们可能会对您的代码产生一些副作用。 这是避免使用
id
属性除非你真的需要它。
归根结底,避免在 JavaScript 中使用命名全局变量可能是个好主意。 我之前引用了规范,说明它是如何导致“脆弱”代码的,但这里有全文来说明这一点:
作为一般规则,依赖于此会导致代码脆弱。 例如,随着新功能添加到 Web 平台,哪些 ID 最终映射到此 API 可能会有所不同。 而不是这个,使用
document.getElementById()
ordocument.querySelector()
.
我认为 HTML 规范本身建议远离此功能这一事实不言而喻。