为什么在删除属性之前使用点表示法检查属性比直接删除属性要快?

8
我提了这个问题,结果发现从元素中删除属性时,先使用elem.xxx!==undefined检查元素是否存在可以使运行时更快。证明

为什么更快?无论你采用何种方式,都需要经过更多的代码并遇到removeAttribute()方法。


2
在我看来,检查属性比调用方法更快。这对我来说并不奇怪。在大多数其他语言中也是如此。 - jfriend00
谢谢@Karl-AndréGagnon,正如你所看到的...我是JS新手,我会在睡觉和醒来后(希望如此)阅读有关.prop的内容。但即使考虑到el.xxxhastAttribute更快,为什么使用el.xxx进行检查然后再删除属性仍然比不经检查直接删除属性更快呢? - dayuloli
@dayuloli,我刚刚删除了我的评论,因为它是错误的,我以为你在使用jQuery。 - Karl-André Gagnon
5
在您的 jsperf 中,它没有调用删除方法,因为该属性不存在于您的 DOM 对象上,即使您将其添加到 DOM 对象中,它也仅会存在于性能基准的第一次迭代中,之后就会消失。 - jfriend00
1
@dayuloli - 你的新jsPerf也有问题,因为"class"属性是通过elem.className而不是elem.class引用的,因为class是Javascript中的保留字。在jsPerf中,你必须做的第一件事就是确保代码在测量性能之前实际上是可行的。 - jfriend00
显示剩余7条评论
4个回答

5
首先需要知道的是,elem.xxx并不等同于elem.getAttribute()或者其他与属性相关的方法。
在DOM元素中,elem.xxx是一个属性,而HTML中的属性和元素在DOM内部也是相似但有所不同。举个例子,看下面这个DOM元素:<a href="#">以及这段代码:
//Let say var a is the <a> tag
a.getAttribute('href');// == #
a.href;// == http://www.something.com/# (i.e the complet URL)

但是让我们来看一个自定义属性:<a custom="test">
//Let say var a is the <a> tag
a.getAttribute('custom');// == test
a.custom;// == undefined

因为它们不能达到相同的结果,所以你无法真正比较两者的速度。但其中一个明显更快,因为属性是一种快速访问数据,而属性使用get/hasAttribute DOM函数。

现在,为什么没有条件会更快呢?简单地说,removeAttribute不关心属性是否缺失,它只检查是否存在。

因此,在removeAttribute之前使用hasAttribute就像做两次检查,但条件会稍微慢一些,因为它需要检查条件是否满足才能运行代码。


1
@jfriend00 错误,if 需要被处理,这就是为什么它稍微慢一点的原因。 - Karl-André Gagnon
1
由于“if”条件从未计算为真,因此不会执行“if”块内部。 - jfriend00
@jfriend00,也许很难理解我的意思,英语是我的第二语言。但我的意思是if()需要更多的计算资源。 - Karl-André Gagnon
@jfriend00,我在尝试说明正在发生的事情。http://pastebin.com/p5YZuQzF - Karl-André Gagnon
@dayuloli 就像我之前所说的,el.xxxgetAttribute 不是同一个东西。el.class 会始终等于 undefined,因为 class 属性应该是 el.className重要的是,属性和点表示法不是同一回事 - Karl-André Gagnon
显示剩余14条评论

2
我怀疑加速提升的原因是跟踪树。
跟踪树最初由加州大学欧文分校的Andreas Gal和Michael Franz在他们的论文《带有跟踪树的增量动态代码生成》中首次引入。
在他的博客文章《追踪网络》中,论文的合著者Andreas Gal解释了跟踪即时编译器的工作原理。
为了尽可能简要地解释跟踪即时编译器(因为我对这个主题的了解不够深入),跟踪即时编译器执行以下操作:
最初,将解释要运行的所有代码。 保持每个代码路径执行的次数计数(例如,执行if语句中true分支的次数)。 当一个代码路径被执行的次数大于预定义的阈值时,该代码路径将被编译成机器码以加速执行(例如,我相信SpiderMonkey执行超过一次执行的代码路径)。现在让我们看看你的代码并了解是什么导致了速度提升:
if (elem.hasAttribute("xxx")) {
    elem.removeAttribute("xxx");
}

这段代码有一个代码路径(即一个if语句)。请记住,跟踪JIT只会优化代码路径而不是整个函数。我认为正在发生以下情况:
  1. 由于代码正在被JSPerf基准测试执行多次(低估了),因此它被编译成机器码。
  2. 然而,它仍然需要额外的函数调用开销hasAttribute,因为它不是条件代码路径(花括号之间的代码)的一部分,所以没有被JIT编译。
  3. 因此,虽然花括号内的代码很快,但条件检查本身很慢,因为它没有被编译,而是被解释。结果就是代码很慢。

测试用例2: 删除

elem.removeAttribute("xxx");

在这个测试用例中,我们没有任何条件代码路径。因此,JIT编译器从未启动。因此,代码很慢。
第三个测试用例:检查(点符号)。
if (elem.xxx !== undefined) {
    elem.removeAttribute("xxx");
}

这与第一个测试用例相同,但有一个重要的区别:
  1. 条件检查是一个简单的非等值检查。因此,它不会产生完整的函数调用开销。
  2. 大多数JavaScript解释器通过假定变量的数据类型固定来优化简单的等值检查。由于elem.xxxundefined的数据类型在每次迭代中都没有改变,这种优化使得条件检查更快。
  3. 结果是,条件检查(虽然被解释执行)不会显著减慢编译代码路径的速度。因此,这段代码是最快的。
当然,这只是我的猜测。我不知道JavaScript引擎的内部情况,因此我的答案不是权威的。但我认为这是一个很好的合理猜测。

1
虽然我不确定这是否正确(正如你自己所说,这只是一个有根据的猜测),但这可以解释一切! - dayuloli

0

正如Karl-André Gagnon所指出的那样,访问[本地] JavaScript属性和调用DOM函数/属性是两个不同的操作。

一些DOM属性通过DOM IDL作为JavaScript属性公开;这些与adhoc JS属性不同,并需要DOM访问。此外,即使DOM属性被公开,也没有与DOM属性的严格关系!

例如,inputElm.value = "x"不会更新DOM属性,即使元素将显示并报告更新后的值。如果目标是处理DOM属性,唯一正确的方法是使用hasAttribute/setAttribute等。


我一直在努力推导出一个“公平”的微基准测试来测试不同的函数调用,但这相当困难,因为有很多不同的优化发生。这里是我的最佳结果,我将用它来支持我的观点。

请注意,没有ifremoveAttribute来混淆结果,我只关注DOM/JS属性访问。此外,我试图排除速度差异仅仅是由于函数调用引起的,并且我分配结果以避免明显的浏览器优化。你的结果可能会有所不同。

观察结果:

  1. 访问 JS 属性 是很 快速 的。这是可以预期的1,2

  2. 调用函数可能比直接属性访问成本更高1,但并不像 DOM 属性DOM 函数 那么慢。也就是说,hasAttribute 并不仅仅是一个 "函数调用" 使它变得如此缓慢。

  3. DOM 属性 访问比本地 JS 属性 访问要;然而,性能在 DOM 属性和浏览器之间有很大差异。我的更新的微基准测试显示了一个趋势,即 DOM 访问 - 无论是通过 DOM 属性还是 DOM 函数 - 可能 比本机 JS 属性访问更慢2

回到最顶部:在元素上访问非DOM [JS]属性与访问相同元素上的DOM属性,更不用说DOM属性之一DOM属性之一,本质上是完全不同的。正是这种根本差异以及浏览器之间方法之间的优化(或缺乏优化),导致了观察到的性能差异。

1 IE 10做了一些聪明的技巧,其中虚假函数调用非常快(我怀疑调用已被省略),即使它具有可怕的JS属性访问。然而,考虑到IE是一个异常值或仅仅是加强函数调用不是引入固有较慢行为的论点,并没有削弱我的主要论点:基本上是DOM访问更慢。

2 我很想说DOM属性访问更慢,但FireFox对input.value进行了一些惊人的优化(但不包括img.src)。这里发生了一些特殊的魔法。Firefox不会优化DOM属性访问。

而且,不同的浏览器可能展示完全不同的结果...然而,我认为至少可以通过隔离我认为是“性能问题”的实际使用DOM来排除任何关于ifremoveAttribute的“魔法”。


0

你的证明是错误的...

elem.class !== undefined 总是评估为 false,所以 elem.removeAttribute("class") 从未被调用,因此,这个测试将始终更快。

elem 上正确使用的属性是 className,例如:

typeof elem.className !== "undefined"

是的。但其他使用了 elem.xxx 的测试编辑也表明它更快。我现在会更新我的“证明”。 - dayuloli

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