Introduction

In the previous HTML DOM — Elements chapter, we got to know about the Element interface of the DOM API, various of its properties and methods, and how to work with them.

In the Text Content Exercise, the Redefining replaceChild() Exercise and the Node Count Exercise, we even practiced some of those concepts and also our understanding of JavaScript, in general.

Now in this chapter, we aim to understand another extremely important concept of the DOM, i.e. working with HTML attributes. Recall that we stated in the previous chapter that there are many methods, and even some properties available an Element and HTMLElement together, that we'll explore in a later chapter specifically meant to deal with HTML attributes. This is that chapter.

In particular, we'll see how to add new attributes to a given element, deleting existing ones, check if an attribute exists already, retrieve the value of a given attribute, work with the extremely common HTML class attribute, the HTML data- category of attributes, and even the HTML id attribute.

We'll also take a look at the now-old-fashioned Attr interface along with the attributes property of Element which were used back then to directly work with attribute nodes in the DOM.

In short, there is just a lot to cover, so let's get going...

Attributes in the DOM

As we all know, an HTML (and XML) document is mainly based on elements. These elements can contain many attributes to further define their characteristics, such as their title, their class, their styles, their interaction, and so on.

Talking about HTML documents specifically, it's practically impossible to imagine even a simple HTML page without using at least one attribute. Attributes are one of the most important aspects of HTML (and even XML).

There are a plethora of attributes that can be defined on HTML elements. Each element has its own set of valid attributes. Many attributes are common amongst many categories of elements but some are only specific to a few of them.

For instance, the placeholder attribute is meant for <input> elements only, usually text input elements. Similarly, the title attribute can be set on almost every element.

The DOM API allows us to obtain access to precisely every single attribute used in a given HTML document.

Depending on the attribute we wish to work with, given an element node, we can either:

  • Use a bunch of methods of the element node to work with the attribute.
  • Use a property of the element node, meant to reflect the attribute.
  • Use a specialized interface, exposed via a property of the element node, to work with the attribute.

In the following sections, we'll explore all three of these.

Let's start with the simplest and most cross-browser compatible way of working with attributes, the first in the list above — methods of the Element interface.

Getting an attribute's value

The getAttribute() method of the Element interface allows us to obtain the value of an arbitrary attribute defined on the calling element.

Here's its syntax:

element.getAttribute(name)

name is the name of the attribute whose value we want to retrieve, treated case-insensitively for HTML documents. Upon success, the method returns that value.

If there is no such attribute on the calling element node whose name is name, getAttribute() returns null.

As the name suggests, getAttribute() returns back the value of the attribute once it completes. Typically, a method whose name begins with 'get' tends to return back a value.

Anyways, let's consider an example.

In the markup below, we've defined exactly three attributes on the <h1> element, namely id, style and contenteditable:

<h1 id="h1" style="color: blue" contenteditable="true">A heading</h1>

In the following code, we access the values of all three of these attributes, with the help of getAttribute():

var h1Element = document.getElementById('h1');

console.log(h1Element.getAttribute('id'));
console.log(h1Element.getAttribute('style'));
console.log(h1Element.getAttribute('contenteditable'));
h1 color: blue true

Note that if the attribute is defined on the element without any value, i.e. it's a Boolean attribute, getAttribute() will return back an empty string ('') as its value.

An example is shown below:

<h1 id="h1" contenteditable>A heading</h1>
var h1Element = document.getElementById('h1')
undefined
h1Element.getAttribute('contenteditable')
''

Simple?

The best thing about getAttribute() is that it has an amazing support across many old browsers. Hence, if we plan to support these old browsers, then we must go for getAttribute().

Setting an attribute

Apart from getting the value of a given attribute, another common concern in programs is to add a new attribute or modify the value of an existing attribute. Both of these tasks are essentially accomplished using the same method — setAttribute().

The setAttribute() method is used to set an attribute to a given value. If the attribute doesn't exist on the calling element, it's added.

The syntax of setAttribute() is pretty predictable:

element.setAttribute(name, value)

name is the name of the attribute while value is its value. Both are treated case-insensitively.

The method returns back nothing (i.e returns the value undefined).

Below shown is an example:

<h1 id="h1">A heading</h1>
.text-blue {
   color: blue
}

We add the class attribute to the <h1> element and set its value to 'text-blue'. This gets its text to be colored blue:

var h1Element = document.getElementById('h1');

// Add a new class attribute.
h1Element.setAttribute('class', 'text-blue');

A heading

It works as expected.

As stated before, setAttribute() doesn't just allow us to add a new attribute to a given element node, but also to update the value of an existing attribute. This is demonstrated below.

<h1 id="h1" class="text-blue">A heading</h1>
.text-blue {
   color: blue
}
.text-red {
   color: red
}

We change the class attribute of the <h1> element from 'text-blue' to 'text-red'. This obviously gets its text to be colored red:

var h1Element = document.getElementById('h1');

// Modify the existing class attribute.
h1Element.setAttribute('class', 'text-red');

A heading

Once again, simple and concise.

Moving on, there is an important thing to keep in mind when setting attributes via setAttribute() and designating them to the value true. It's detailed as follows:

Watch out for the attribute value true!

One important case to keep in mind when working with setAttribute() is when working with Boolean attributes, such as contenteditable.

We can go on and set such an attribute to true using setAttribute(), however this value would be coerced into the string 'true' and then set as the value of the attribute in the DOM. Now here comes the problem — if the HTML engine doesn't recognize this string value of the attribute, it would have no effect.

Rather, what we should do in this case is to either set the attribute to the name of the attribute (i.e. disabled="disabled") or set it to an empty string.

The HTML engine recognizes empty attribute values and treats them as if the underlying attribute is present. This doesn't hold for the Boolean true as we just learnt a while ago.

Checking for attributes

Often times rather than retrieving the value of a given attribute, we're more interested in figuring out whether or not it really even exists on a particular element.

This can be done very easily using the hasAttribute() method.

As its name suggests, hasAttribute() returns back a Boolean value indicating whether the calling element node has a given attribute present or not.

Here's its syntax:

element.hasAttribute(name)

As before, name is the name of the attribute that concerns us.

Let's quickly consider a basic example:

<div id="main">Some text</div>
var mainElement = document.getElementById('main')
undefined
mainElement.children[0].hasAttributes()
false
mainElement.children[1].hasAttributes()
true

JavaScript also provides us with a more generic method to check if an element node has any attribute at all, not just a specific one. That method is hasAttributes().

Once again, the name is pretty self-explanatory in the purpose of the method and even its signature.

That is, hasAttributes() tells us whether or not the calling element has any attributes at all. Moreover, as for its signature (i.e syntax), it doesn't require any arguments, quite obviously.

Here's its syntax:

element.hasAttributes()

Consider the example below:

<div id="main">
   <p>Paragraph 1</p>
   <p title="Paragraph 2">Paragraph 2</p>
</div>
var mainElement = document.getElementById('main')
undefined
mainElement.children[0].hasAttributes()
false
mainElement.children[1].hasAttributes()
true

The first <p> element inside #main has no attributes at all, likewise the first hasAttributes() calls yields false. However, there is a title attribute present on the second <p> element, and so likewise the second log is true.

If hasAttributes() returns false, then clearly hasAttribute() would return false as well for literally any given argument.

Removing an attribute

The removeAttribute() method allows us to remove an attribute from an element node.

If the attribute doesn't exist on the element, removeAttribute() ignore silently and doesn't throw any error.

Its syntax is also pretty much predictable:

element.removeAttribute(name)

We just ought to remove a given attribute, likewise only its name is required. As with setAttribute(), removeAttribute() also returns back nothing.

Time to consider an example.

In the code below, we remove the class attribute from #main and thus get the corresponding CSS styles applied with that class to be removed:

<h1 id="h1" class="text-blue">A heading</h1>
.text-blue {
   color: blue
}
var h1Element = document.getElementById('h1');

// Remove the class attribute.
h1Element.removeAttribute('class');

A heading

As can be seen, the <h1> element has the default black color applied, which means that the class attribute has been removed from it.

To further testify this fact, let's use the hasAttribute() method on h1Element to check for the existence of the class attribute:

h1Element.hasAttribute('class')
false

Not surprisingly, we get false returned, testifying the fact that there is really no class attribute present on the <h1> element.

The classList property

The classList property is a specialized feature to work with the HTML class attribute in the DOM.

The reason for providing a specialized feature to work with class is quite apparent — the attribute is used more than just frequently in HTML documents.

It's very common to designate particular styles to given HTML classes in the CSS and then use these classes on given HTML elements to trigger the rendering of those styles.

Once we cover the Events API, in the next JavaScript Events unit, we'll see that this is one of the mainstream tasks in JavaScript programs, i.e. to add/remove given classes from an element to trigger the rendering of particular styles. It's sometimes also required to toggle a class on an element, i.e to remove it if it exists, or to add it otherwise.

In short, the class attribute alone requires a bunch of utilities to ease the process of adding/removing/toggling given classes. This inspires the DOM API to provide us with a separate feature to work with class, and that feature is the classList property of the Element interface.

The classList property returns back an instance of the DOMTokenList interface. DOMTokenList is a means of working with a given attribute in terms of a set of tokens which are simply strings.

Now what are tokens?

Well, recall the HTML class attribute. It can have multiple class names inside it, separated via the space character. These individual class names are what the DOM calls tokens.

For instance in the attribute definition class="text-blue text-red", there are two token present, i.e. text-blue and text-red.

The weird name: DOMTokenList

The name DOMTokenList, according to the WHATWG DOM specification itself, is a 'legacy mishap'. It actually represents a set, containing a bunch of unique tokens, yet its name gets us to rather think of it as a list of elements.

A much better name would have been DOMTokenSet, but we couldn't make this change now, given that DOMTokenList has been adopted extensively across various DOM implementations.

Legacy often hurts in programming.

Anyways, coming back to DOMTokenList, it provides us with the following properties and methods to work with an attribute's value:

  • length — the total number of tokens in the underlying token set.
  • value — the stringified value of the underlying token set.
  • add() — adds a new token to the underlying token set.
  • remove() — removes a given token from the underlying token set.
  • toggle() — adds or removes a token based on its existence in the underlying token set.
  • contains() — checks whether the underlying token set contains a particular token.
  • forEach() — iterates over all the tokens in the underlying token set, invoking the callback function provided as an argument.

Let's consider a bunch of examples to better understand DOMTokenList as exposed by the classList property.

In the code below, we add a new class to the <h1> element by calling the add() method on its classList property:

<h1 id="h1">A heading</h1>
.text-blue {
   color: blue
}
var h1Element = document.getElementById('h1');

// Add the class 'text-blue'.
h1Element.classList.add('text-blue');

A heading

Even though there is no class attribute present on <h1> before calling classList.add(), the method takes care of that itself.

In the second example below, we remove the class 'text-blue' from <h1> by calling classList.remove():

<h1 id="h1" class="text-blue">A heading</h1>
.text-blue {
   color: blue
}
var h1Element = document.getElementById('h1');

// Remove the class 'text-blue'.
h1Element.classList.remove('text-blue');

A heading

The color of <h1> here clearly confirms the fact that the class text-blue has been removed from it. However, keep in mind that the class attribute is still there on the element — it's just that the text-blue token is removed from its value.

In the snippet below, we confirm this fact:

h1Element.hasAttribute('class')
true

As the return value true indicates, <h1> still has the class attribute on it.

Moving on, now let's see classList.contains() in action.

In the code below, we perform a couple of checks on the class attribute of <h1>:

<h1 id="h1" class="text-blue padded LARGE">A heading</h1>
var h1Element = document.getElementById('h1');
undefined
h1Element.classList.contains('text-blue')
true
h1Element.classList.contains('TEXT-BLUE')
false
h1Element.classList.contains('LARGE')
true
h1Element.classList.contains('large')
false
h1Element.classList.contains('padded')
true
h1Element.classList.contains(' padded   ')
false
h1Element.classList.contains('text-red')
false
h1Element.classList.contains('class')
false

This snippet contains a lot more information than might be observed in the first glance:

  • Tokens are matched case-sensitively. That is, if a token in the HTML element's class attribute is PADDED, then only the string 'PADDED' would match it when passed to contains().
  • The string passed to contains() isn't trimmed for whitespace before begin checked for existence in the underlying token set. That is, given that the token PADDED exists in the underlying token set of a DOMTokenList instance, only the string 'PADDED' will match it.
  • contains() only checks if a given token exists in the value of a given element's class attribute. It doesn't check if the class attribute itself exists or not.

Simple?

The dataset property

If you've worked with HTML for quite a while, then you would surely know about the category of HTML data- attributes. They are a standard way or defining custom attributes on HTML elements.

For instance, we could represent all the data of an <ol> element as a comma-delimited string of items in the data-list attribute, as shown below:

<h1>Some programming languages</h1>

<ol data-list="Python,JavaScript,PHP"></ol>

Using various methods of the DOM API, we can read and thereby process this data-list attribute to actually fill the <ol> element with corresponding <li> elements. We'll demonstrate this shortly below.

It's not a necessity to call it data-list — we could also call it data-items, or data-collection, or just about anything meaningful.

The DOM API obviously allows us to retrieve the values of these attributes, and even set them, by means of the getAttribute() and setAttribute() methods, as we've discussed them before.

However, what we haven't discussed thus far is that the DOM API even provides a specialized property on element nodes to operate on its data- attributes. That is dataset.

The dataset property of an element node (or precisely speaking, of the HTMLElement interface) allows us to work with data- attributes on the element. It returns back a DOMStringMap instance.

The DOMStringMap interface is solely meant to ease the process of working with data- attributes. It doesn't have other applications apart from operating on data- attributes.

DOMStringMap is an exotic interface, if we are to refer to it in the argot of the ECMAScript specification. Let's see what this means, in detail:

DOMStringMap is exotic!

Do you recall what's an exotic object from the previous chapters?

Well, an exotic object (or interface) is one which doesn't follow the normal internal utilities of objects. The arguments object is an example of an exotic object.

Talking about DOMStringMap, it's exotic in the sense that it doesn't treat property get, set and delete expressions normally. Let's take property-get as an example to help illustrate what we mean by this.

In a normal object obj, when we get a property using an expression such as obj.prop, the value of the underlying property is returned, just as it's stored in memory (assuming that we're talking about a data property).

However, in the case of a DOMStringMap instance obj, when we get a property using an expression obj.prop, an internal function is called that forms the return value right at the moment, by getting the value of the data-prop attribute from the calling element node.

This is exotic behavior when getting a property on a DOMStringMap object.

For this same reason, there are no predefined properties or methods available on DOMStringMap. If there were, the interface would've had to separately deal with those predefined properties and methods when we retrieved them, so as to not trigger its internal property-retrieval utility on them.

Using dataset, we can get the values of, set (i.e. update or add) and even remove data- attributes from a given element node.

  • Getting an attribute's value is as simple as getting a property on dataset.
  • Setting an attribute is as simple as setting a property on dataset using the assignment operator (=).
  • As for removing an attribute, it's as simple as deleting a property from dataset (via the delete keyword).

It's worth mentioning here that there is a name conversion that happens each time when we get/set/delete a data- attribute using the dataset property.

This conversion happens by virtue of the fact that the word data is stripped from the corresponding property name (on dataset) representing a data- attribute.

That is, to access the data-list attribute on a given element node element, we'd write the following:

element.dataset.list

Notice that the property name list, which represents the data-list attribute, doesn't have the word data in it. The word is automatically added when by DOMStringMap when we set the property, or is automatically truncated when we retrieve the property.

In addition to this, the name conversion also happens because DOMStringMap doesn't allow accessing properties whose name includes a hyphen (-) character in it, and so we have to use some other methodology of working with data- attributes that are comprised of multiple words separated by hyphens (-).

That is, we can't do the following to set a data-some-thing attribute on a given element node element:

element.dataset['some-thing'] = value

The string 'some-thing' inside the pair of brackets ([]) represents a property name which, although isn't invalid for JavaScript objects otherwise, is considered an illegal property name by the DOMStringMap interface just because it contains a hyphen (-) in it.

So the methodology that DOMStringMap uses is as follows:

To access a data- attribute that consists of multiple words, e.g. data-list-id, the corresponding property name is expressed in camel casing, whereby the words in the casing are the ones separated by a hyphen (-) in the attribute's name, except for the word data.

For instance, to access data-list-id, we'd use dataset.listId. The words taken from the attribute are list and id (excluding the very first word data). When we apply the camel casing on these words, we get listId.

As another example, to access data-list-update-command, we'd write dataset.listUpdateCommand.

Alright, it's time to consider some examples.

In the code below, we retrieve the value of the data-list attribute on the <ol> element and then convert it into an array using the split() string method:

<h1>Some programming languages</h1>

<ol id="list" data-list="Python,JavaScript,PHP"></ol>
var listElement = document.getElementById('list');

var list = listElement.dataset.list.split(',');
console.log(list);
['Python', 'JavaScript', 'PHP']

Now, let's consider setting a data- attribute using dataset.

In the code below, we add a data-list-id attribute to the <ol> element and set its value to '1580'. This is just some arbitrary value applied to an arbitrary attribute for the sake of an example — there isn't any practical importance of the attribute or the particular value '1580':

<h1>Some programming languages</h1>

<ol id="list" data-list="Python,JavaScript,PHP"></ol>
var listElement = document.getElementById('list');

// Add a data-list-id attribute to <ol>.
listElement.dataset.listId = '1580';

console.log(listElement.getAttribute('data-list-id'));
1580

The expression listElement.dataset.listId = '1580' sets a data-list-id attribute on the <ol> element and makes its value '1580'.

In the next statement, we manually retrieve this attribute's value via getAttribute() to see if it really exists on <ol> or not. And it turns out, based on the log, that it does exist.

Polyfilling DOMStringMap

Polyfilling DOMStringMap wasn't possible back then when there was absolutely no way to tap into the internals of JavaScript.

However, these days thanks to the Proxy and Reflect interfaces, it's entirely possible to polyfill DOMStringMap. But the thing is that almost all browsers that support Proxy and Reflect support DOMStringMap as well.

In other words, there is absolutely no need of polyfilling DOMStringMap on newer browsers. For browsers that do require a polyfill, there is no way to create one.

Properties mirroring HTML attributes

So far, we've seen two properties available on element nodes in an HTML DOM tree that allow us to work with given attributes, i.e. classList (a property of the Element interface) that operates on the class attribute, and dataset (a property of the HTMLElement interface) that operates on data- attributes.

Apart from these, almost all HTML attributes of an element are exposed to the end user by means of similarly-named properties. In this section, we'll cover two such properties:

  1. id — mirrors the id attribute.
  2. className — mirrors the class attribute.

In the latter part of this course, we'll cover almost all of the frequently-used properties of HTMLElement nodes in an HTML DOM tree, with each property, or set of properties, dedicated a different unit or chapter.

Anyways, let's come back to the discussion and begin with id.

id

All Element nodes have an id property available on them.

This id property is meant to act as a mirror for the id attribute on the element node.

To be a 'mirror' means that when the property is retrieved in a get context, the corresponding attribute's value is returned back; and similarly when the property is assigned a value, the corresponding attribute's value is changed to that value.

As with almost all properties in the DOM API, id is an accessor property. It has associated getter and setter functions that perform the tasks mentioned above, respectively.

In the code below, we set the id attribute on the <h1> element to the value 'title' by setting the id property on its element node to 'title':

<div id="main">
   <h1>A heading</h1>
   <p>A paragraph</p>
</div>
#title {
   font-size: 50px;
   color: blue;
}
var mainElement = document.getElementById('main');

// Set the id attribute of <h1> to 'title'.
mainElement.firstElementChild.id = 'title';

The addition of id="title" to <h1> would get the CSS shown above to be put into action. Thus the output of this code would be as follows:

A heading

A paragraph

Moving on, we can even use the id property of an element node to empty its id attribute. This is demonstrated below.

In the following example, we set the id attribute of <h1> to the value '' by setting the id property on its corresponding element node to '':

<div id="main">
   <h1 id="title">A heading</h1>
   <p>A paragraph</p>
</div>
#title {
   font-size: 50px;
   color: blue;
}
var h1Element = document.getElementById('title');

// Empty the id attribute of <h1>.
h1Element.id = '';

Quite expectedly, as the code above runs, the 'title' id is removed from <h1>, and thus the styles associated with it get removed as well, which ultimately gives us the following output:

A heading

A paragraph

One important thing to keep in mind regarding the code above is that we retrieved the <h1> element using its id="title" attribute, but removed it later on (or precisely speaking, just emptied the id attribute).

Now we might think that this would lead to the element node h1Element becoming null but that's NOT the case.

Once we have obtained a node, as we did above when we called document.getElementById('title'), then even if we change a characteristic of the node that was used to obtain it in the first place (like we emptied the id of <h1>, which was actually used to obtain it), we still have access to the node.

The node doesn't just become null surprisingly.

Surely, if we inspect the characteristic later on after changing it on the element node, it would be changed. For instance, in the following console snippet, we inspect the value of the id attribute on h1Element via getAttribute():

h1Element.getAttribute('id')
''
h1Element.hasAttribute('id')
true

As we can see, the attribute's value is clearly empty (but the attribute itself is still there, as confirmed by the hasAttribute() call).

Stating it once again, a changed characteristic doesn't mean that the underlying node would become null. It will be the same node (in memory) before and after the change of a characteristic.

Simple?

className

Second in the list we have className.

Despite its strange name, className is the corresponding property for the HTML class attribute.

Let's first settle down the most common question asked regarding className: why is it called className, and not just class?

Well, the answer is quite intuitive.

Why is className not called class?

At least these days, JavaScript engines don't throw an error if we set the name of a property to the name of a reserved keyword of JavaScript.

However, back then, this was actually the case. Technically speaking, class wasn't actually a keyword in old JavaScript implementations, they did include it in their list of reserved keywords, terming it (and a couple more words such as private, public, static, etc.) as 'might be used in the future'.

Thus, it was outright invalid to name a property as class, and thus the property meant to mirror the HTML class attribute was named as className.

If this isn't enough to convince us, we should also recall that the DOM API was, and still is, meant to be implemented in any language apart from JavaScript. In these languages as well, class is a reserved keyword, plus there is also no guarantee that setting a property's name to class is a valid action.

Likewise, the standardization committee at W3C working on the DOM specification (long ago) chose className as the name of the property mirroring the HTML class attribute.

Coming back to the className property, it allows us to get the value of the class attribute, as well as set its value.

The Attr interface

In the chapter HTML DOM — Basics, we learnt about the Attr interface but only on the outskirts. In this section, we'll explore a little bit more about this old-fashioned interface.

As we know by now, the DOM tree is built upon nodes — we have a document node containing element nodes, text nodes, comment nodes, and let's not forget about attribute nodes. All the attributes present in a given HTML/XML document are also represented in the DOM tree as nodes.

In particular, attribute nodes are represented by the Attr interface which extends Node.

Attr defines a bunch of properties specifically meant for attributes. The table below details some of these properties. (Obviously we don't need to learn all of the properties.)

PropertyPurpose
localNameThe name of the attribute.
valueThe value of the attribute.
ownerElementThe element node that owns the attribute, i.e. the one on which the attribute is defined.
ownerDocumentThe document node that contains the ownerElement of the attribute.

Attr nodes are part of the DOM tree, but they don't (and can't) have any parent (and thus no siblings) or any children. They are owned by given element nodes and are accessible via the attributes property on those element nodes. We'll come to this in a while.

But first let's see how to create an attribute node.

The createAttribute() factory method of the Document interface creates and returns back an Attr instance. The name of the attribute is specified as an argument to the method, which is then converted to lowercase when creating the attribute.

The value of the attribute is provided via the value property of the returned Attr instance.

Let's consider an example.

In the code below, we create a class attribute node and then set its value to 'highlighted':

var classAttribute = document.createAttribute('class');
classAttribute.value = 'highlighted';

This code only creates a new attribute node but doesn't add it to an element.

To add an attribute node to an element, we use the setAttributeNode() method of the Element interface.

Notice the word 'Node' at the end of the name of this method. It indicates that the method is meant to deal with nodes.

setAttributeNode() accepts a given Attr node and then adds the corresponding attribute to the calling element or updates it if it already exists (just like how setAttribute() operates).

Unlike setAttribute(), setAttributeNode() returns back a value which is either the replaced attribute node, if there was any attribute present on the calling element node with the same name, or else null.

In the example below, we add the attribute node that we just created above to our <h1> element:

<h1 id="h1">A heading</h1>
.highlighted {
   background-color: yellow
}
var classAttribute = document.createAttribute('class');
classAttribute.value = 'highlighted';

// Add class="highlighted" to <h1>.
var h1Element = document.getElementById('h1');
h1Element.setAttributeNode(classAttribute);

A heading

And voila! The code works.

Similarly, to get an attribute node or remove an attribute node from an element, we are provided with the methods getAttributeNode() and removeAttributeNode(), respectively.

Now as you might have noticed, directly working with attribute nodes to get/set/delete attributes from given elements in the DOM is really not a simple task. It unnecessarily complicates the code, leaving us tangled in multiple Attr instances where we could've just relied on working with mere strings.

For this reason, you'll sparingly notice any programs these days operating directly on Attr nodes. It won't be wrong to say that the Attr interface has gone out of fashion. We're much better off if we stick to using getAttribute(), setAttribute() and removeAttribute().

It's a good thing now that we at least know about the Attr interface and how to directly work with attribute nodes, but remember that there is no need to use this interface — it just adds clutter to the code.