为什么我们不能只使用元素ID作为JavaScript中的标识符?

67

我所接触的所有浏览器都允许通过简单地写下以下代码来访问具有id="myDiv"的元素:

myDiv

请看这里:http://jsfiddle.net/L91q54Lt/

无论如何,这种方法似乎文档很差,实际上,我遇到的来源甚至没有提及它,而是假定使用

document.getElementById("myDiv")

或许

document.querySelector("#myDiv")

访问DOM元素时,即使预先知道其ID(即在运行时未计算),也可以使用短形式。我可以说后一种方法的优点是,如果某人在更广泛的范围内不经意地尝试重新定义myDiv,覆盖它并继续而没有注意到冲突,代码仍然保持安全(虽然这不是一个很好的想法...)。但除此之外还有其他问题吗?在使用上述短形式方面是否存在任何问题,或者我还缺少了什么?

11
一个窗口属性(例如 myDiv)可以是任何东西(注意它可能会被覆盖),但是 getElementById 返回 HTMLElement(不管属性的值为何)。 - Dr.Molle
3
@Jack:那个问题没有提到通过全局变量访问具有ID的HTML元素,而这个问题正是关于这个的。因此,我不认为“高度相关”完全正确。 - Paul D. Waite
5
紧密相关:https://dev59.com/-3A75IYBdhLWcg3wK1wp。这段文本需要翻译成中文。 - Qantas 94 Heavy
1
@Jack:也许吧。我认为找到那个重复的问题比写答案更费力。请注意,重复的问题只在最后一句话中提出了这个问题。当阅读它及其答案时,您必须浏览初始内容(所有浏览器都支持此功能吗?)才能获得对此问题的答案(为什么这样做是一个坏主意?)。“不要重复自己”是编程的一个伟大原则。它不是普遍的常数。重复并不总是邪恶的。 - Paul D. Waite
1
@Jack:啊,应该给予信用。"第一!"的合法战利品。是的,这值得去做。但如果回答很快,原帖作者会很快得到他们的答案,如果有类似问题的不同措辞,我认为这意味着再次提出相同问题的人将更有可能在自动搜索中找到匹配项。 - Paul D. Waite
显示剩余7条评论
5个回答

53

无论如何,这种方法似乎文档记录很差,事实上,我遇到的来源甚至没有提到它[...]

除了依赖隐式声明的全局变量外,缺乏文档是不使用它的一个很好的理由。

明显将id值提升为全局变量并非符合标准的(HTML5规范中ID属性没有提到它),因此,您不应该假设未来的浏览器会实现它。

编辑:事实证明这种行为符合标准 - 在HTML5中,window应支持对“命名元素”的属性访问:

根据上述算法,具有名称name的命名对象是那些:

  • 活动文档的子浏览上下文其名称是name的,
  • a、applet、area、embed、form、frameset、img或object元素具有名称内容属性,其值为name,或者
  • 具有id内容属性,其值为name的HTML元素。

来源:HTML 5规范,“window对象上的命名访问”我强调

基于此,符合标准不是避免使用这种模式的原因。但是,规范本身建议不要使用它:

一般来说,依赖于此会导致脆弱的代码。随着Web平台添加新功能,将哪些ID映射到此API可能会随时间而变化。因此,不要使用这种方式,应使用document.getElementById()document.querySelector()


22
规范为什么会规定某些东西,然后又建议不要使用它呢?好像网络还需要更多的不良做法一样。 #捂脸# - Songo
9
我猜这是为了正式支持那些已经成为常见用法的旧模式而采取的措施。 - joews
2
据我所知,name != id(必须),我从未找到任何明确说明不能同时具有名称为“foo”的1个元素和ID为“foo”的另一个元素的文档(可能错过了),因此增加了1个更高级别的歧义。在GTK3的API中,我没有看到同时使用名称和ID的意义 - 直到将它们分开破坏了大量东西(特别是gtkbuilder xml UI)。 - technosaurus
2
@Izkata 我知道,你不想从自己的错误中学习。很多 gtkbuilder 和 gtk3 样式都是基于 html 和 css 模型设计的,然后当他们决定使用 javascript 作为构建 gtk3 应用程序的第一语言时,他们修改了旧有的 name~=id 行为,这破坏了很多东西... 引用 name 和 id 作为全局变量的能力非常相似,我认为在将来的版本中,要么 name 完全消失,要么 name 获得全局处理(许多非 js 浏览器的遗留表单依赖它),而 id 则需要被获取(仅是猜测)。 - technosaurus
3
鉴于这次编辑和更新,我认为没有理由不使用这个功能。 - ESR
显示剩余5条评论

21

很好的问题。正如爱因斯坦可能没有说过的那样,“事情应该尽可能简单,但不过于简单。”

后一种方法的优点在于,如果有人无意中试图在更广泛的范围内重新定义 myDiv(虽然这不是一个明智的做法...),或者用某个不同的值覆盖它并继续而没有注意到冲突,代码也能保持安全。

这就是为什么这是个坏主意的主要原因,已经足够了。全局变量不安全可靠。它们可以被任何时候的任何脚本覆盖。

除此之外,仅仅输入 myDiv 不是 document.getElementById() 的“缩写”。它是对全局变量的引用。document.getElementById() 如果元素不存在将会返回null,而试图访问不存在的全局变量会抛出引用错误,因此您需要使用 try/catch 块来包装对全局变量的引用以确保安全。

这也是为什么 jQuery 如此流行的原因之一:如果您执行 $("#myDiv").remove(),并且没有具有 id 为 myDiv 的元素,则不会抛出错误——代码只会静默地不执行任何操作,这通常正是您在进行 DOM 操作时想要的结果。


2
如果元素不存在,document.getElementById() 返回的是 null 而不是 undefined - GOTO 0
1
@GOTO0: 噢,谢谢,谢谢。我知道我应该检查一下的。 - Paul D. Waite
7
具有讽刺意味的是,无声失败也是人们不喜欢 jQuery 的原因之一。 - Ryan Kinal
@RyanKinal 赶紧失败,廉价失败。这就是我一直说的。 - Capaj

16

有几个原因:

你不想让你的代码和标记耦合在一起。

通过使用特定的调用来访问div,您不必担心全局空间被破坏。 添加一个声明myDiv在全局空间的库将会非常痛苦且难以修复。

您可以通过ID访问不属于DOM的元素

它们可以在片段、帧或未分离并且尚未重新附加到DOM的元素中。

编辑:访问未附加的元素的ID示例

var frag = document.createDocumentFragment();
var span = document.createElement("span");
span.id = "span-test";
frag.appendChild(span);
var span2 = frag.getElementById("span-test");
alert(span === span2);


1
看起来你错了:无论是Firefox还是Chromium都不允许我通过ID访问那些不属于DOM的元素。至少是那些分离的元素。 - user
@user 你不能使用 document.getElementById 访问它们,不过我更新了我的回答来展示如何通过 iddocumentFragment 中访问一个 span - Jeremy J Starcher
我将所有的ID命名为UPPERCASE_ID,这大大降低了冲突的可能性,并使它们更易于识别。 - run_the_race

2
在我的情况下,我在页面内有一个 iframe。我对 id 属性和 name 属性感到困惑,两者都影响了一个名为 inner_iframe 的变量,可以从 window 访问!
  • 如果我只使用了 id 属性,例如 id="inner_iframe",那么 window.inner_iframe 就是一个 HTMLIFrameElement。它的属性包括 inner_iframe.contentDocumentinner_iframe.contentWindow,详情请参见 这里*

  • 如果我只使用了 name 属性,例如 name="inner_iframe",那么 window.inner_iframe 就是一个 "frame",也就是一个 "window 对象"。因此,使用 name 属性的 inner_iframe 没有 contentDocumentcontentWindow 属性。

  • 如果我同时使用了 nameid 属性,并且给两个属性赋予了相同的值 name="inner_iframe" id="inner-iframe",那么 name 属性会覆盖 id 属性;我最终得到的是 "window 对象",而不是 HTMLIFrameElement
我的意思是要小心歧义;在同一对象上有两个不同的API,nameid属性之间的冲突:只是一个特定的情况,其中隐式行为和对窗口变量的附着可能会使您感到困惑。

*(仅当<script>在HTML中加载在<iframe>之后/下面,或者<script>在尝试按id属性访问之前等待window.onload时)

**Mozilla文档中描述了“frame”与DOM元素区别:

window.frames伪数组中的每个项目都表示与给定<iframe><frame>内容相对应的窗口对象,而不是(i)frame DOM元素(即,window.frames [0]与document.getElementsByTagName(“iframe”)[0] .contentWindow是相同的)。


1
作为一个开发基于Web的应用程序超过25年的人,我可以肯定地说,这种古怪行为的唯一原因是向后兼容性。
方法getElementById甚至不是DOM Level 1的一部分。然而,早期的DOM Level 0允许直接访问页面上的大多数元素:

https://docstore.mik.ua/orelly/webprog/dhtml/ch02_04.htm

如您所见,即使在getElementById被采用为“标准”之后,像Netscape 6和MSIE 5这样的浏览器仍然继续支持旧的约定。
最早支持DHTML的浏览器,如Netscape Navigator和MSIE,经常将自己的DOM规范推向市场。因此,您最终会遇到许多糟糕的设计决策,这些决策最终通过“这就是以前的做法”而成为标准。

https://www.quirksmode.org/js/dom0.html

由于向后兼容的原因,即使支持 Level 1 DOM 的更先进的浏览器仍然支持旧的、可靠的 Level 0 DOM。不支持它意味着最常见的脚本突然无法工作了。因此,尽管 Level 0 DOM 不完全符合新的 DOM 概念,浏览器仍将继续支持它。
当 Netscape 推出 Level 0 DOM 时,可能认为基于 Web 的应用程序永远不会超过自动填充几个表单字段和在屏幕上移动一些图像。因此,没有关注全局 window 对象中有一些元素 ID 的问题。
显然,这种解决方案根本不具有可扩展性。但我们仍然被困在这个古老的 Level 0 DOM 约定中,只是为了不破坏传统的 Javascript 代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接