8

I asked this question, and it turned out that when removing an attribute from an element, checking whether the element exists first using elem.xxx!==undefined makes the runtime faster. Proof.

Why is it quicker? There's more code to go through and you'll have to encounter the removeAttribute() method whichever way you go about this.

Machavity
  • 30,841
  • 27
  • 92
  • 100
dayuloli
  • 16,205
  • 16
  • 71
  • 126
  • 2
    Looks to me like checking a property is faster than calling a method. Not surprising to me. The same would be true in most any other language. – jfriend00 Mar 21 '14 at 17:31
  • But it's not just checking an attribute, it's checking an attribute and then calling a method to remove it. That is faster than simply removing the attribute without checking. It boggles my mind. – dayuloli Mar 21 '14 at 17:34
  • Thanks @Karl-AndréGagnon, as you can tell...I'm new to JS, I will read about `.prop` after I sleep and wake up (hopefully). But even given that `el.xxx` will be faster than `hastAttribute`, why is checking using `el.xxx` and then removing the attribute still faster than removing the attribute without checking? – dayuloli Mar 21 '14 at 17:37
  • @dayuloli i deleted my comment, it was wrong, i though you were in jquery. – Karl-André Gagnon Mar 21 '14 at 17:38
  • 5
    In your jsperf, it's NOT calling the method to remove it because the attribute doesn't exist on your DOM object and even if you add it to the DOM object, it will only be there for the first iteration of the performance benchmark and afterwards will be gone. – jfriend00 Mar 21 '14 at 17:39
  • @jfriend00 I have updated the question to reflect your comment – dayuloli Mar 21 '14 at 18:03
  • @dayuloli - GEEZ. Now you completely changed the meaning and answer of the question. I'm out of here - not worth working on such a moving target. – jfriend00 Mar 21 '14 at 18:08
  • 1
    @dayuloli - also your new jsPerf doesn't work properly because the `"class"` attribute is referenced by `elem.className` not `elem.class` because `class` is a reserved word in Javascript. The #1 thing you have to do in a jsPerf is to make sure the code is actually working before you measure its performance. – jfriend00 Mar 21 '14 at 18:09
  • @jfriend00 I'm so sorry I am new to this and don't know how to phrase things properly...so sorry for the misunderstanding. The JSPerf thing - someone else set that up I just nicked it. – dayuloli Mar 21 '14 at 18:11
  • In the book Learning Javascript - A Hands-On Guide to the Fundamentals of Modern Javascript, it uses `hasAttribute("class")` – dayuloli Mar 21 '14 at 18:13
  • Yes, you use `hasAttribute("class")`. But you don't use `elem.class`. – jfriend00 Mar 21 '14 at 18:28
  • @jfriend00 arrr! Thank you for your patience with me! – dayuloli Mar 21 '14 at 18:32

4 Answers4

5

Well, first thing you need to know is that elem.xxx is not the same as elem.getAttribute() or any other method relative to the attribute.

elem.xxx is a property of a DOM element while attribute and element on the HTML inside the DOM, both are similar but different. For exemple, take this DOM element: <a href="#"> and this code :

//Let say var a is the <a> tag
a.getAttribute('href');// == #
a.href;// == http://www.something.com/# (i.e the complet URL)

But let take a custom attribute : <a custom="test">

//Let say var a is the <a> tag
a.getAttribute('custom');// == test
a.custom;// == undefined

So you can't really compare the speed of both since they don't achieve the same result. But one is clearly faster since properties are a fast access data while attribute use the get/hasAttribute DOM functions.

Now, Why without the condition is faster? Simply because removeAttribute doesn't care is the attribute is missing, it check if it is not.

So using hasAttribute before removeAttribute is like doing the check twice, but the condition is a little slower since it need to check if the condition is satisfied to run the code.

Karl-André Gagnon
  • 33,662
  • 5
  • 50
  • 75
  • But the `if` statements are NEVER satisfied in the OP's jsPerf because the attribute/property simply doesn't exist in their jsPerf so discussing the speed of anything inside the `if` block is irrelevant. The OP's jsPerf is nothing more than a test if `if (elem.prop !=== undefined)` vs. `if (elem.hasAttribute('prop'))`. – jfriend00 Mar 21 '14 at 17:51
  • 1
    @jfriend00 False, the `if` need to be processed, hence why it is juste a little bit slower. – Karl-André Gagnon Mar 21 '14 at 17:52
  • 1
    Inside the `if` block is never executed because the `if` condition never evaluates to truthy. – jfriend00 Mar 21 '14 at 17:53
  • @jfriend00 it is never satisfied yes, but it need to check if **it is satisfied** while using only `removeAttribute`doesn't have to do that. – Karl-André Gagnon Mar 21 '14 at 17:54
  • @user2864740 - which is exactly what I said in answer which you proceeded to downvote. This whole jsPerf is ONLY about testing `hasAttribute` vs. a direct property check and the two aren't even necessarily checking the same thing. – jfriend00 Mar 21 '14 at 17:54
  • @Karl-AndréGagnon - your point might make some sense if the attribute actually exists, but it doesn't and the case that calls `removeAttribute()` directly is the slowest of all. – jfriend00 Mar 21 '14 at 17:55
  • @jfriend00 it is maybe hard to follow what im saying, english is my second language. but what im saying is that the `if()` take some more juice. – Karl-André Gagnon Mar 21 '14 at 17:58
  • @Karl-AndréGagnon I think you have with the [JS] property vs [DOM] function difference - For instance, the same difference can be seen in the case without an if-statement or attribute removal. – user2864740 Mar 21 '14 at 17:59
  • Please see http://jsperf.com/checkattributeornot/6, here the attribute does exist but checking it exists with dot notation it's still faster than removing the attribute outright. Thanks for Karl-Andre trying to help me learn JS! – dayuloli Mar 21 '14 at 18:00
  • @jfriend00 here, im trying to illustrate what is happening http://pastebin.com/p5YZuQzF – Karl-André Gagnon Mar 21 '14 at 18:04
  • @user2864740 - see here: http://jsperf.com/prop-vs-method. Calling `getAttribute()` is way slower than a direct property reference for attributes that are reflected as properties (which is where you have a choice). It's slower in all browsers, but look even more so at Firefox and IE. – jfriend00 Mar 21 '14 at 18:05
  • @Karl-AndréGagnon Irrelevant. That may be a *very small* as in (.1% difference), see http://jsperf.com/checkattributeornot/4 - only the hasAttribute cases are slow, even when the attribute always exists (no `if`, no `removeAttribute`) – user2864740 Mar 21 '14 at 18:05
  • @user2864740 I do not understand your last comment..? – Karl-André Gagnon Mar 21 '14 at 18:07
  • @Karl-AndréGagnon `if(true)` takes *no significant time*. The reason lies in what *you've already said*: the JS property access is *not* the same as the DOM attribute access. Accessing the DOM is fundamentally slower. (Only some special JS properties as outlined in the DOM IDL are exposed.) – user2864740 Mar 21 '14 at 18:09
  • @Karl-AndréGagnon I get that checking with `hasAttribute()` makes it slower because there's one more step, but why does adding an extra step using dot-notation makes it faster? – dayuloli Mar 21 '14 at 18:09
  • @dayuloli as i said, `el.xxx` is not the same as `getAttribute`. `el.class` will always == undefined since the property for `class` is `el.className`. **the important thing is that attribute and dot-notation is not the same thing** – Karl-André Gagnon Mar 21 '14 at 18:14
  • And even when the class is not specified in the DOM node, `className === ''`, hence `if(el.className !== undefined)` will always be true. – Karl-André Gagnon Mar 21 '14 at 18:15
  • @Karl-AndréGagnon I get your point now after reading this - http://stackoverflow.com/a/10673539/2317532 – dayuloli Mar 21 '14 at 18:31
  • @dayuloli maybe that will also help : http://stackoverflow.com/questions/6003819/properties-and-attributes-in-html – Karl-André Gagnon Mar 21 '14 at 18:34
2

I have a suspicion that the reason for the speed boost are trace trees.

Trace trees were first introduced by Andreas Gal and Michael Franz of the University of California, Irvine, in their paper Incremental Dynamic Code Generation with Trace Trees.

In his blog post Tracing the Web Andreas Gal (the co-author of the paper) explains how tracing Just-in-Time compilers works.

To explain tracing JIT compilers as sententiously as possible (since my knowledge about the subject isn't profound) a tracing JIT compiler does the following:

  1. Initially all the code to be run is interpreted.
  2. A count is kept for the number of times each code path is executed (e.g. the number of times the true branch of an if statement is executed).
  3. When the number of times a code path is taken is greater than a predefined threshold the code path is compiled into machine code to speed up execution (e.g. I believe SpiderMonkey executes code paths executed more than once).

Now let's take a look at your code and understand what is causing the speed boost:

Test Case 1: Check

if (elem.hasAttribute("xxx")) {
    elem.removeAttribute("xxx");
}

This code has a code path (i.e. an ifstatement). Remember that tracing JITs only optimize code paths and not entire functions. This is what I believe is happening:

  1. Since the code is being benchmarked by JSPerf it's being executed more than once (an understatement). Hence it is compiled into machine code.
  2. However it still incurs the overhead of the extra function call to hasAttribute which is not JIT compiled because it's not a part of the conditional code path (the code between the curly braces).
  3. Hence although the code inside the curly braces is fast the conditional check itself is slow because it's not compiled. It is interpreted. The result is that the code is slow.

Test Case 2: Remove

elem.removeAttribute("xxx");

In this test case we don't have any conditional code paths. Hence the JIT compiler never kicks in. Thus the code is slow.

Test Case 3: Check (Dot Notation)

if (elem.xxx !== undefined) {
    elem.removeAttribute("xxx");
}

This is the same as the first test case with one significant difference:

  1. The conditional check is a simple non-equivalence check. Hence it doesn't incur the full overhead of a function call.
  2. Most JavaScript interpreters optimize simple equivalence checks like this by assuming a fixed data type for both the variables. Since the data type of elem.xxx or undefined is not changing every iteration this optimization makes the conditional check even faster.
  3. The result is that the conditional check (although interpreted) does not slow down the compiled code path significantly. Hence this code is the fastest.

Of course this is just speculation on my part. I don't know the internals of a JavaScript engine and I hence my answer is not canonical. However I opine that it is a good educated guess.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 1
    Although I am not sure whether this is correct (as you said so yourself, an educated guess). This would explain everything! – dayuloli Mar 21 '14 at 18:20
0

Your proof is incorrect...

elem.class !== undefined always evaluates to false and thus elem.removeAttribute("class") is never called, therefore, this test will always be quicker.

The correct property on elem to use is className, e.g.:

typeof elem.className !== "undefined"
Graham
  • 14,885
  • 4
  • 36
  • 42
  • Yes. But other tests edits where `elem.xxx` was used also showed it is faster. I'll update my 'proof' now – dayuloli Mar 21 '14 at 19:07
0

As Karl-André Gagnon pointed out, accessing a [native] JavaScript property and invoking a DOM function/property are two different operations.

Some DOM properties are exposed as JavaScript properties via the DOM IDL; these are not the same as adhoc JS properties and require DOM access. Also, even though the DOM properties are exposed, there is not strict relation with DOM attributes!

For instance, inputElm.value = "x" will not update the DOM attribute, even though the element will display and report an updated value. If the goal is to deal with DOM attributes, the only correct method is to use hasAttribute/setAttribute, etc.


I've been working on deriving a "fair" micro-benchmark for the different function calls, but it is fairly hard and there is alot of different optimization that occurs. Here my best result, which I will use to argue my case.

Note that there is no if or removeAttribute to muddle up the results and I am focusing only on the DOM/JS property access. Also, I attempt to rule out the claim that the speed difference is merely due to a function call and I assign the results to avoid blatant browser optimizations. YMMV.

Observations:

  1. Access to a JS property is fast. This is to be expected1,2

  2. Calling a function can incur a higher cost than direct property access1, but is not nearly as slow as DOM properties or DOM functions. That is, it is not merely a "function call" that makes hasAttribute so much slower.

  3. DOM properties access is slower than native JS property access; however, performance differs widely between the DOM properties and browsers. My updated micro-benchmark shows a trend that DOM access - be it via DOM property or DOM function - may be slower than native JS property access2.

And going back to the very top: Accessing a non-DOM [JS] property on an element is fundamentally different than accessing a DOM property, much less a DOM attribute, on the same element. It is this fundamental difference, and optimizations (or lack thereof) between the approaches across browsers, that accounts for the observed performance differences.


1 IE 10 does some clever trick where the fake function call is very fast (and I suspect the call has been elided) even though it has abysmal JS property access. However, considering IE an outlier or merely reinforcement that the function call is not what introduces the inherently slower behavior, doesn't detract from my primary argument: it is the DOM access that is fundamentally slower.

2 I would love to say DOM property access is slower, but FireFox does some amazing optimization of input.value (but not img.src). There is some special magic that happens here. Firefox does not optimize the DOM attribute access.

And, different browsers may exhibit entirely different results .. however, I don't think that one has to consider any "magic" with the if or removeAttribute to at least isolate what I believe to be the "performance issue": actually using the DOM.

user2864740
  • 60,010
  • 15
  • 145
  • 220