浏览器之前一直有个奇怪的设定:带有 ID
的 DOM
元素可以直接在 JavaScript
中作为全局变量进行访问。
不知道大家之前了不了解这个,反正我是最近才知道,但是它一直在 Web
环境中是个固定存在的事实。你可以直接在浏览器做个测试,找到一个带有 ID 的元素:
<div id="ConardLi"></div>
通常,我们会使用 querySelector("#ConardLi")
或 getElementById("ConardLi")
定义一个新变量来选择这个元素:
var element = querySelector("#ConardLi");
但我们实际上已经可以直接在没有这种繁琐代码的情况下访问 #ConardLi
:
所以,HTML
中的任何 id
(或 name
属性)都可以在 JavaScript
中使用 window[ELEMENT_ID]
直接访问。再强调一次,这并不是一个 “新的” 功能,但确实不是很常见。很显然,这使用命名引用来访问全局作用域并不是个好主意 ,它很容易对我们的全局作用域造成 “污染”。
HTML 规范
中概述了这种方法,其中将其描述为 “Named access on the Window object
”。
Internet Explorer
是第一个实现这个功能的浏览器,后来所有其他浏览器也对它提供了支持。Gecko
内核是当时唯一不直接在标准模式下支持它的浏览器,而是选择将其作为实验性功能。Gecko
甚至试图说服 WebKit
将其移出标准模式,但最终还是妥协了,并最终在 Firefox 14 中进入标准模式。
因为有全局变量污染的风险,浏览器必须要采取一些预防措施来确保生成的全局变量不会破坏我们的网站,其中一项措施叫 “Variable shadowing
”。
听起来挺高大上,实际上就是命名元素生成的全局变量引用不会覆盖现有的全局变量,所以如果 DOM 元素具有 ID 已定义为全局的元素,它不会把现有的变量覆盖掉,比如:
<head>
<script>
window.foo = "17";
</script>
</head>
<body>
<div id="foo">I won't override window.foo</div>
<script>
console.log(window.foo); // "17"
</script>
</body>
反之亦然:
<div id="foo">I will be overridden :(</div>
<script>
window.foo = "17";
console.log(window.foo); // "17"
</script>
这种行为是至关重要的,因为它会使 <div id="alert" />
这样危险的代码不会覆盖掉原生的 alert API
。正是因为这种保护技术的存在,很有可能就是我们最近才了解到这一点的原因。
简单搜了一下,其实有很多文章批评过这种写法,比如这篇 12 年的老文章说的就比较中肯:
<a id="cool">
但某些浏览器(即 Safari 和 Firefox)ReferenceError在控制台中返回 a 。DOM
中有多个相同命名元素的实例时 (例如 <div class="cool">
的两个实例),浏览器应该返回一个包含实例数组的 HTMLCollection
。但是,Firefox
只返回第一个实例。另外我还想到一些其他可能引发的问题,比如在我们实现某个 Polyfill
的时候,我们首先会去判断当前的浏览器环境支不支持它:
<body>
<img id="cookieStore"></img>
<script>
// 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");
</script>
</body>
这段代码在 Chrome
中运行得很好,但在 Safari
中会抛出以下错误:
TypeError: cookieStore.set is not a function
Safari
目前没有对 cookieStore
提供支持,当我们试图去给他添加一个 Polofill 时,会因为页面上已经有了一个 id 为 cookieStore
的 img 标签而无法添加成功。
另外,浏览器 JavaScript API 的更新也是可能会破坏命名元素的全局引用的,例如:
<body>
<input id="BarcodeDetector"></input>
<script>
window.BarcodeDetector.focus();
</script>
</body>
当前这段代码是正常工作的,但是我们无法预测它还能工作多久,加入浏览器增加了一个原生 API 就叫 BarcodeDetector
,那这段代码就 GG ...
所以今天的结论:就算你知道了通过全局变量可以直接访问页面上的 DOM ,也尽量别用!
参考: