Issue
This question's answer (addClass to an element with that class already?) indicates that when using jQuery there is no problem that arises if you .addClass('foo')
on an element that already has a class of foo
.
I am curious of the same is true for the element.classList
method of .add
.
In particular I have a function called update()
that is called whenever a range slider is updated. This can occur many times a second. Depending on the value passed to update()
I add and remove certain classes to an element.
If the value falls in a certain range consecutively I end up adding the same class over and over again.
My question is can I allow elem.classList.add('foo')
to run, let's say, 50 times in one second without experiencing any negative consequences to user experience, memory, processor use, etc. Is this an acceptable practice?
Thanks.
Solution
DON'T do this:
if (!el.classList.contains("foo")) {
el.classList.add("foo");
}
DO this:
el.classList.add("foo");
Reason:
- The
.classList
property is a DOMTokenList object. - It represents a set of whitespace-separated tokens.
- The tokens (i.e. the class names) are case-sensitive.
- The term "set" in programming refers to a container that holds a unique collection of items, in unspecified order.
- In other words, a set cannot contain duplicates.
- The DOM specification which defines the
DOMTokenList
defines it as a container with case-sensitive, whitespace-trimmed, unique items. - All methods that read or modify the
DOMTokenList
(such asDOMTokenList.add()
) automatically trim any surrounding whitespace and remove duplicates from the set. - Meaning that if you try to
add()
the same className twice, it won't add the duplicate.
Here's an example:
<span class=" d d e f"></span>
// Output: DOMTokenList(3) ["d", "e", "f", value: " d d e f"]
// As you can see, it only contains the unique values.
console.log(document.querySelector("span").classList);
// Try to add a duplicate value.
document.querySelector("span").classList.add("e");
// Output: DOMTokenList(3) ["d", "e", "f", value: "d e f"]
// As you can see again, it only contains the unique values.
console.log(document.querySelector("span").classList);
// In fact, the act of duplicates being detected while trying
// to add more classes, results in the "class" attribute itself
// being cleaned up and trimmed:
// Output: "d e f"
console.log(document.querySelector("span").getAttribute("class"));
Alright, so why, exactly, shouldn't you use .contains()
to "avoid calling .add()
when unnecessary"? Because:
- The
.contains()
function and the.add()
functions use the exact same native code for searching through the unique set to see if it contains the class. - Therefore there's no speed difference between their techniques for detecting whether a className exists.
- In other words, calling
.add()
will search through the set, and if it already contains the className, the function simply aborts. And yes, it sometimes also cleans up the "class" HTML attribute if necessary, but only if necessary. - On the other hand, if you call
.contains()
before.add()
and you end up adding the className, you just ran the search for the className twice, via two different functions. - But more importantly: You make your code clunky and unreadable by having that huge
if (!el.classList.contains("foo")) { ... }
boilerplate around every attempt to add classes. - The DOM spec was made to be modern and performant for the use-cases programmers need it for. That means, that you should be using
.add()
to add a class (even if it already exists), and.remove()
to remove a class (even if it's already missing).
Don't waste your keyboard's life and programmer eyes on clunky boilerplate. Only use .contains()
if you actually care about finding out if an element has a certain class. 😊
Also keep in mind that the DOMTokenList
has many other helpful functions:
- See https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList.
.replace(oldToken, newToken)
: Replaces an existing token with a new token. If the old token doesn't exist,.replace()
returnsfalse
immediately, without adding the new token to the token list..toggle(token [, force])
: Removes a given token from the list and returnsfalse
, or if token doesn't exist it's instead added and the function returnstrue
. Alternatively, if you include theforce
parameter: If included, turns the toggle into a one way-only operation. If set tofalse
, then token will only be removed, but not added. If set totrue
, then token will only be added, but not removed.- Those two extra functions will let you implement most of things you'd ever wanna do instead of "if not .contains() then .add()" or similar code. You should ONLY be using ".contains()" if YOU personally need to know if a token exists.
Answered By - Mitch McMabers
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.