When to use setAttribute vs .attribute= in JavaScript?

When to use setAttribute vs .attribute= in JavaScript?

Has a best-practice around using setAttribute instead of the dot (.) attribute notation been developed?
E.g.:
myObj.setAttribute(“className”, “nameOfClass”);
myObj.setAttribute(“id”, “someID”);

or
myObj.className = “nameOfClass”;
myObj.id = “someID”;

Solutions/Answers:

Solution 1:

You should always use the direct .attribute form (but see the quirksmode link below) if you want programmatic access in JavaScript. It should handle the different types of attributes (think “onload”) correctly.

Use getAttribute/setAttribute when you wish to deal with the DOM as it is (e.g. literal text only). Different browsers confuse the two. See Quirks modes: attribute (in)compatibility.

Solution 2:

From Javascript: The Definitive Guide, it clarifies things. It notes that HTMLElement objects of a HTML doc define JS properties that correspond to all standard HTML attributes.

So you only need to use setAttribute for non-standard attributes.

Example:

node.className = 'test'; // works
node.frameborder = '0'; // doesn't work - non standard attribute
node.setAttribute('frameborder', '0'); // works

Solution 3:

None of the previous answers are complete and most contain misinformation.

There are three ways of accessing the attributes of a DOM Element in JavaScript. All three work reliably in modern browsers as long as you understand how to utilize them.

1. element.attributes

Elements have a property attributes that returns a live NamedNodeMap of Attr objects. The indexes of this collection may be different among browsers. So, the order is not guaranteed. NamedNodeMap has methods for adding and removing attributes (getNamedItem and setNamedItem, respectively).

Notice that though XML is explicitly case sensitive, the DOM spec calls for string names to be normalized, so names passed to getNamedItem are effectively case insensitive.

Example Usage:

var div = document.getElementsByTagName('div')[0];

//you can look up specific attributes
var classAttr = div.attributes.getNamedItem('CLASS');
document.write('attributes.getNamedItem() Name: ' + classAttr.name + ' Value: ' + classAttr.value + '<br>');

//you can enumerate all defined attributes
for(var i = 0; i < div.attributes.length; i++) {
  var attr = div.attributes[i];
  document.write('attributes[] Name: ' + attr.name + ' Value: ' + attr.value + '<br>');
}

//create custom attribute
var customAttr = document.createAttribute('customTest');
customAttr.value = '567';
div.attributes.setNamedItem(customAttr);

//retreive custom attribute
customAttr = div.attributes.getNamedItem('customTest');
document.write('attributes.getNamedItem() Name: ' + customAttr.name + ' Value: ' + customAttr.value + '<br>');
<div class="class1" id="main" data-test="stuff" nonStandard="1234"></div>

2. element.getAttribute & element.setAttribute

These methods exist directly on the Element without needing to access attributes and its methods but perform the same functions.

Again, notice that string name are case insensitive.

Example Usage:

var div = document.getElementsByTagName('div')[0];

//get specific attributes
document.write('Name: class Value: ' + div.getAttribute('class') + '<br>');
document.write('Name: ID Value: ' + div.getAttribute('ID') + '<br>');
document.write('Name: DATA-TEST Value: ' + div.getAttribute('DATA-TEST') + '<br>');
document.write('Name: nonStandard Value: ' + div.getAttribute('nonStandard') + '<br>');


//create custom attribute
div.setAttribute('customTest', '567');

//retreive custom attribute
document.write('Name: customTest Value: ' + div.getAttribute('customTest') + '<br>');
<div class="class1" id="main" data-test="stuff" nonStandard="1234"></div>

3. Properties on the DOM object, such as element.id

Many attributes can be accessed using convenient properties on the DOM object. Which attributes exist depends on the DOM node’s type, not which attributes are defined in the HTML. The properties are defined somewhere in the prototype chain of DOM object in question. The specific properties defined will depend on the type of Element you are accessing. For example, className and id are defined on Element and exist on all DOM nodes that are elements (ie. not text or comment nodes). But value is more narrow. It’s defined on HTMLInputElement and may not exist on other elements.

Related:  How to test 'private' functions in an angular service with Karma and Jasmine

Notice that JavaScript properties are case sensitive. Although most properties will use lowercase, some are camelCase. So always check the spec to be sure.

This “chart” captures a portion of the prototype chain for these DOM objects. It’s not even close to complete, but it captures the overall structure.

                      ____________Node___________
                      |               |         |
                   Element           Text   Comment
                   |     |
           HTMLElement   SVGElement
           |         |
HTMLInputElement   HTMLSpanElement

Example Usage:

var div = document.getElementsByTagName('div')[0];

//get specific attributes
document.write('Name: class Value: ' + div.className + '<br>');
document.write('Name: id Value: ' + div.id + '<br>');
document.write('Name: ID Value: ' + div.ID + '<br>'); //undefined
document.write('Name: data-test Value: ' + div.dataset.test + '<br>'); //.dataset is a special case
document.write('Name: nonStandard Value: ' + div.nonStandard + '<br>'); //undefined
<div class="class1" id="main" data-test="stuff" nonStandard="1234"></div>

Caveat: This is an explanation of how the HTML spec defines and modern browsers handle attributes. I did not attempt to deal with limitations of ancient, broken browsers. If you need to support old browsers, in addition to this information, you will need to know what is broken in the those browsers.

Solution 4:

One case I found where setAttribute is necessary is when changing ARIA attributes, since there are no corresponding properties. For example

x.setAttribute('aria-label', 'Test');
x.getAttribute('aria-label');

There’s no x.arialabel or anything like that, so you have to use setAttribute.

Edit: x[“aria-label”] does not work. You really do need setAttribute.

x.getAttribute('aria-label')
null
x["aria-label"] = "Test"
"Test"
x.getAttribute('aria-label')
null
x.setAttribute('aria-label', 'Test2')
undefined
x["aria-label"]
"Test"
x.getAttribute('aria-label')
"Test2"

Solution 5:

“When to use setAttribute vs .attribute= in JavaScript?”

A general rule is to use .attribute and check if it works on the browser.

..If it works on the browser, you’re good to go.

..If it doesn’t, use .setAttribute(attribute, value) instead of .attribute for that attribute.

Rinse-repeat for all attributes.

Well, if you’re lazy you can simply use .setAttribute. That should work fine on most browsers. (Though browsers that support .attribute can optimize it better than .setAttribute(attribute, value).)

Solution 6:

These answers aren’t really addressing the large confusion with between properties and attributes. Also, depending on the Javascript prototype, sometimes you can use a an element’s property to access an attributes and sometimes you can’t.

Related:  Why does jQuery show/hide use display:none instead of visibility:hidden?

First, you have to remember that an HTMLElement is a Javascript object. Like all objects, they have properties. Sure, you can create a property called nearly anything you want inside HTMLElement, but it doesn’t have to do anything with the DOM (what’s on the page). The dot notation (.) is for properties. Now, there some special properties that are mapped to attributes, and at the time or writing there are only 4 that are guaranteed (more on that later).

All HTMLElements include a property called attributes. HTMLElement.attributes is a live NamedNodeMap Object that relates to the elements in the DOM. “Live” means that when the node changes in the DOM, they change on the JavaScript side, and vice versa. DOM attributes, in this case, are the nodes in question. A Node has a .nodeValue property that you can change. NamedNodeMap objects have a function called setNamedItem where you can change the entire node. You can also directly access the node by the key. For example, you can say .attributes["dir"] which is the same as .attributes.getNamedItem('dir'); (Side note, NamedNodeMap is case-insensitive, so you can also pass 'DIR');

There’s a similar function directly in HTMLElement where you can just call setAttribute which will automatically create a node if it doesn’t exist and set the nodeValue. There are also some attributes you can access directly as properties in HTMLElement via special properties, such as dir. Here’s a rough mapping of what it looks like:

HTMLElement {
  attributes: {
    setNamedItem: function(attr, newAttr) { 
      this[attr] = newAttr;
    },    
    getNamedItem: function(attr) {
      return this[attr];
    },
    myAttribute1: {
      nodeName: 'myAttribute1',
      nodeValue: 'myNodeValue1'
    },
    myAttribute2: {
      nodeName: 'myAttribute2',
      nodeValue: 'myNodeValue2'
    },
  }
  setAttribute: function(attr, value) { 
    let item = this.attributes.getNamedItem(attr);
    if (!item) {
      item = document.createAttribute(attr);
      this.attributes.setNamedItem(attr, item);
    }
    item.nodeValue = value;
  },
  getAttribute: function(attr) { 
    return this.attributes[attr] && this.attributes[attr].nodeValue;
  },
  dir: // Special map to attributes.dir.nodeValue || ''
  id:  // Special map to attributes.id.nodeValue || ''
  className: // Special map to attributes.class.nodeValue || '' 
  lang: // Special map to attributes.lang.nodeValue || ''

}

So you can change the dir attributes 6 ways:

  // 1. Replace the node with setNamedItem
  const newAttribute = document.createAttribute('dir');
  newAttribute.nodeValue = 'rtl';
  element.attributes.setNamedItem(newAttribute);

  // 2. Replace the node by property name;
  const newAttribute2 = document.createAttribute('dir');
  newAttribute2.nodeValue = 'rtl';
  element.attributes['dir'] = newAttribute2;
  // OR
  element.attributes.dir = newAttribute2;

  // 3. Access node with getNamedItem and update nodeValue
  // Attribute must already exist!!!
  element.attributes.getNamedItem('dir').nodeValue = 'rtl';

  // 4. Access node by property update nodeValue
  // Attribute must already exist!!!
  element.attributes['dir'].nodeValue = 'rtl';
  // OR
  element.attributes.dir.nodeValue = 'rtl';

  // 5. use setAttribute()  
  element.setAttribute('dir', 'rtl');

  // 6. use the UNIQUELY SPECIAL dir property
  element["dir"] = 'rtl';
  element.dir = 'rtl';

You can update all properties with methods #1-5, but only dir, id, lang, and className with method #6.

Related:  How to dynamically change header based on AngularJS partial view?

Extensions of HTMLElement

HTMLElement has those 4 special properties. Some elements are extended classes of HTMLElement have even more mapped properties. For example, HTMLAnchorElement has HTMLAnchorElement.href, HTMLAnchorElement.rel, and HTMLAnchorElement.target. But, beware, if you set those properties on elements that do not have those special properties (like on a HTMLTableElement) then the attributes aren’t changed and they are just, normal custom properties. To better understand, here’s an example of its inheritance:

HTMLAnchorElement extends HTMLElement {
  // inherits all of HTMLElement
  href:    // Special map to attributes.href.nodeValue || ''
  target:  // Special map to attributes.target.nodeValue || ''
  rel:     // Special map to attributes.ref.nodeValue || '' 
}

Custom Properties

Now the big warning: Like all Javascript objects, you can add custom properties. But, those won’t change anything on the DOM. You can do:

  const newElement = document.createElement('div');
  // THIS WILL NOT CHANGE THE ATTRIBUTE
  newElement.display = 'block';

But that’s the same as

  newElement.myCustomDisplayAttribute = 'block';

This means that adding a custom property will not be linked to .attributes[attr].nodeValue.

Performance

I’ve built a jsperf test case to show the difference: https://jsperf.com/set-attribute-comparison. Basically, In order:

  1. Custom properties because they don’t affect the DOM and are not attributes.
  2. Special mappings provided by the browser (dir, id, className).
  3. If attributes already exists, element.attributes.ATTRIBUTENAME.nodeValue =
  4. setAttribute();
  5. If attributes already exists, element.attributes.getNamedItem(ATTRIBUTENAME).nodeValue = newValue
  6. element.attributes.ATTRIBUTENAME = newNode
  7. element.attributes.setNamedItem(ATTRIBUTENAME) = newNode

Conclusion (TL;DR)

  • Use the special property mappings from HTMLElement: element.dir, element.id, element.className, or element.lang.

  • If you are 100% sure the element is an extended HTMLElement with a special property, use that special mapping. (You can check with if (element instanceof HTMLAnchorElement)).

  • If you are 100% sure the attribute already exists, use element.attributes.ATTRIBUTENAME.nodeValue = newValue.

  • If not, use setAttribute().