为什么document.all的值为假?

79

document.all是在DOM中的非原始对象,其值为假。

例如,以下代码并没有实际作用:

if (document.all) {
    alert("hello");
}

有人能解释一下为什么会这样吗?


现代浏览器不再实现这个过时的东西了。这是IE的“标准”,Opera也会“模拟”它。 - Bergi
@Nanne 这个问题是:有人能解释一下为什么这段代码什么也没做吗?如果它没有被实现,if语句会返回false,什么也不会发生。因此我认为这就是答案。 - user1150525
但问题也说明我们正在处理一个非空对象?也许我读错了,但我认为这意味着在测试中它存在,只是没有触发? - Nanne
@Nanne:好的,我现在明白问题了。我添加了另一个答案。 - Bergi
Kyle Simpson的书中有一个很好的tl;dr解释:https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch4.md#falsy-objects(向下滚动到“为什么?!”标题)。 - imrek
2019年的Chrome 75版本仍然具有document.all == null。为什么?仍然保持这个的意义是什么? - MgSam
5个回答

128

声明: 我是那个发推问这个问题的人 :) 这是我在 Front-Trends 演讲中提出并回答的问题。在上台前五分钟,我写下了那个推文。


我提出的问题如下:

ECMAScript 规范定义了 ToBoolean() 方法如下

ToBoolean(condition), slide from my Front-Trends 2012 talk

可以看到,所有非原始对象(即不是布尔值、数字、字符串、undefinednull 的对象)根据规范都被视为真值。然而,在 DOM 中,有一个例外——一个假值的 DOM 对象。你知道它是哪个吗?

答案是 document.all。根据HTML 规范

属性 all 必须返回一个以 Document 节点为根的 HTMLAllCollection,该集合包含所有元素。

返回的对象有几个不寻常的行为:

用户代理必须像在 JavaScript 中使用 ToBoolean() 运算符将返回的对象转换为 false 值一样。

用户代理必须像在 JavaScript 中对于 ==!= 运算符的目的一样,在处理返回的对象时,将其视为等于 undefined 值。

用户代理必须使 JavaScript 中的 typeof 运算符应用于返回的对象时返回字符串 'undefined'

这些要求是有意违反当时写作的 JavaScript 规范(ECMAScript 第5版)。JavaScript 规范要求 ToBoolean() 运算符将所有对象转换为 true 值,并且没有规定对象在某些运算符中充当类似 undefined 的角色。这种违反是出于与两类旧内容兼容的愿望:一类使用 document.all 的存在来检测旧的用户代理,另一类只支持那些旧的用户代理,并在未先测试其是否存在的情况下使用 document.all 对象。

因此,document.all 是唯一一个官方例外符合 ECMAScript 规则。(在 Opera 中,document.attachEvent 等也是假值,但这并没有在任何地方规定。)

上述文本解释了为什么要这样做。但这里有一个非常常见的旧网页示例代码片段,可以进一步说明这一点:

if (document.all) {
  // code that uses `document.all`, for ancient browsers
} else if (document.getElementById) {
  // code that uses `document.getElementById`, for “modern” browsers
}

基本上,很长一段时间以来,人们通常使用document.all来检测旧浏览器。然而,由于document.all在测试时优先级较高,因此即使是那些同时提供这两种属性的现代浏览器,也会最终进入document.all代码路径。当然,在现代浏览器中,我们更喜欢使用document.getElementById,但由于大多数浏览器仍然具有document.all(出于其他向后兼容性的原因),如果document.all为真值,则else将永远不会被访问。如果代码编写方式不同,这将不会成为问题:

if (document.getElementById) {
  // code that uses `document.getElementById`, for “modern” browsers
} else if (document.all) {
  // code that uses `document.all`, for ancient browsers
}

但是不幸的是,许多现有的代码却做了相反的事情。

这个问题的最简单解决方法是在仍然模仿它的浏览器中,将 document.all 设为假值。


7
一个关于已过时功能的相当复杂的答案。 - adrianp
33
欢迎来到互联网,由于历史遗留问题,所有事情都变得很复杂 :) - Mathias Bynens
但是在2017年10月,他们已经删除了这个通知,尽管行为一直保持不变,直到今天... - Jyrkka
5
好的,我会尽力为您进行翻译。以下是您需要翻译的内容:And so we have https://mariusschulz.com/blog/nullish-coalescing-the-operator-in-typescript#compiled-output-checking-for-null-and-undefined smh - wils
1
有趣。我认为这是因为Netscape从未实现document.all。你必须使用document.getElementById。而对于IE,您必须使用document.all。因此,没有“更新”的或“较旧”的方法来执行此操作,它们只是竞争浏览器,因此没有特定的原因使document.getElementById条件首先出现。在某个时候(可能是Chrome出现时?)document.all再次开始工作,但只是部分工作,因为您无法再对其进行测试... - mike nelson

24

ES2019 Update

现在对象有一个[[IsHTMLDDA]]内部插槽

实现定义的对象上可能存在[[IsHTMLDDA]]内部插槽。具有[[IsHTMLDDA]]内部插槽的对象在 ToBoolean 和 Abstract Equality Comparison 抽象操作以及用作 typeof 运算符的操作数时的行为类似于 undefined。

HTML标准也已经更新,将该内部插槽添加到实现HTMLAllCollection接口的对象中:

实现HTMLAllCollection接口的对象是具有另一个[[Call]]内部方法的遗留平台对象,在下面的章节中进行了描述。它们还具有一个[[IsHTMLDDA]]内部插槽。


在HTML标准中,这种疯狂的做法的原因在于以下注释:

这些特殊的行为是由于与两类遗留内容兼容性的需求而产生的:一种使用document.all的存在作为一种检测旧用户代理的方式,另一种仅支持这些旧用户代理并在没有先测试其存在性的情况下使用document.all对象。

因此,基本上标准想要与这两种类型的代码兼容:

  • 检查是否在Internet Explorer中运行以使用其非标准功能的代码,例如document.all和Activex;

    if (document.all) {
        useActiveXStuff();
    }
    
  • 假设代码在Internet Explorer中运行,并使用document.all

  • document.all["my-button"].onclick = function () {
        alert("hi");
    };
    

4
似乎DDA代表“Document Dot All”。 - Thai

8
简单来说,这是为了使这两个代码示例都能正常运行。浏览器必须这样做,以便旧的网页仍然可以正常工作。

示例 1

// Internet Explorer
if (document.all) {
    useActiveX()
}
// Netscape Navigator
else {
    useOldButStillWorkingCode()
}

示例 2

document.all.output.innerHTML = 'Hello, world!'

8
现代浏览器不再实现这个过时的东西。它是由IE引入的,但大多数其他浏览器"模拟"它以保持兼容性。
为了实现浏览器检测(在旧时代,您可以通过测试document.all来区分IE和NN),同时支持document.all语法,其他浏览器采用了"奇怪"的实现方式,使typeof document.all返回undefined。
Opera> document.all
// prints the array-like object
Opera> typeof document.all
"undefined"
Opera> Boolean(document.all)
false

在FF停止支持document.all之前,正如这条信息中所述,它也表现出奇怪的行为。您可以在Mozilla bug #412247中找到更多内部细节。
W3C邮件列表归档中还有一个非常长的线程,起始于http://lists.w3.org/Archives/Public/public-html/2009Jun/0546.html

4
其他答案已经很好地解释了为什么document.all会表现出这样的行为。
然而,我非常好奇更多关于历史背景的知识。document.all来自哪里,为什么现代浏览器至今仍支持它?
因此,我进行了一些研究以了解更多信息,以下是我的发现。 document.all最初是在Internet Explorer 4中引入的。它的主要用途是通过ID访问元素,就像这样:
var element = document.all[id]

后来,W3C标准化了document.getElementById作为一种通过ID获取元素的方法。

然而,由于IE多年来拥有最大的市场份额,许多网站仅使用document.all而没有进行测试。

其中一些网站非常受欢迎,但它们会在除IE之外的浏览器中出现故障。

因此,开始讨论在其他浏览器中添加对document.all的支持,以便使用document.all的网站可以在这些浏览器中正常工作。

只是为了给你一些讨论的例子,这里有两个来自bugzilla的讨论:

所以,最终,其他浏览器开始实现 document.all

然而,由于网站使用 document.all 来检测 IE,使用类似这样的 if 语句:

if (document.all) {
  // Use proprietary Internet Explorer APIs
}

为防止其他浏览器被误判为 Internet Explorer,W3C 规范化了 document.all 作为行为类似于未定义的 falsy 对象。
因此,截至2023年,document.all仍然在所有主流浏览器中得到支持。你可能会问为什么?可能是因为他们希望旧网站可以正常工作。
我实际上制作了一个关于 document.all 及其历史的两分钟 YouTube 视频,如果你感兴趣,请看看:https://youtu.be/KFasyUpmoss

document.all


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