When to use setAttribute vs .attribute= in JavaScript?

141,912

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 properties exist on a given object depends on the object's DOM node type, regardless of which attributes are specified in the HTML. The available properties are defined somewhere in the prototype chain of DOM object in question. So, the specific properties that are 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, but not text or comment nodes. value is more narrowly defined. It only available on HTMLInputElement and it's descendants.

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 demonstrates 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 define attributes and how modern, evergreen browsers handle them. There certainly are old browsers (IE, Netscape, etc.) that didn't adhere to or even predated the spec. If you need to support old ancient (broken) browsers, you'll need more information than provided here.

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

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.

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.

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().

Share:
141,912

Related videos on Youtube

Francisc
Author by

Francisc

Updated on December 11, 2021

Comments

  • Francisc
    Francisc over 2 years

    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";
    
    • Andrew
      Andrew over 4 years
      When I switched from .setAttribute() to [key] = value, everything started magically working.
  • olan
    olan over 10 years
    @Aerovistae - agree with you on this. Added a new answer which is hopefully clearer.
  • Michael
    Michael over 10 years
    and furthermore, it appears after the last setAttribute in your example, node.frameborder is NOT defined, so you must getAttribute to get the value back.
  • Michael
    Michael over 10 years
    But if you want to affect the innerHTML of the element, you have to use setAttribute...
  • olan
    olan over 10 years
    @Michael correct - if you use setAttribute to set a value, you must use getAttribute to retrieve it.
  • megawac
    megawac about 10 years
    You mean outterHTML* :)
  • aaaaaaaaaaaa
    aaaaaaaaaaaa almost 10 years
    There is nothing wrong with setting frameBorder directly, but note the capitalization. Someone thought it was a jolly good idea to camelCase the JavaScript equivalents of HTML attributes. I haven't managed to find any specification for this, but the net seems to agree that it is a matter of 12 specific cases (for HTML 4 at least). See for instance the following post: drupal.org/node/1420706#comment-6423420
  • Plastic Rabbit
    Plastic Rabbit over 9 years
    I found that a.href returns full url, but getAttribute('href') returns exactly what in that attribute (<a href="/help" ...).
  • Muhammad Umer
    Muhammad Umer about 9 years
    what do you mean by doesn't work. Like it doesn't show up in dom...what if that's fine with you and you are justing using it to store say timers in it. node.timerforclick=fun.... then clearInterval(node.timerforclick)
  • Fareed Alnamrouti
    Fareed Alnamrouti almost 9 years
    actually not really in Javascript you can do this x["aria-label"]
  • Antimony
    Antimony almost 9 years
    @fareednamrouti That doesn't work. I just tested it. The JS properties don't affect the html attributes. You really do need setAttribute here.
  • Fareed Alnamrouti
    Fareed Alnamrouti almost 9 years
    @Antimony This is strange but Yes you are 100% right i will vote up
  • mseifert
    mseifert over 8 years
    The usemap attribute can't be set using the dot notation when creating the map dynamically for an image. It requires img.setAttribute('usemap', "#MapName"); Does your answer imply that usemap is therefore "non-standard"?
  • Mecki
    Mecki over 8 years
    This should be the accepted answer. E.g. if you want to set onchange attribute of a DOM node, that only works with setAttribute.
  • Ben
    Ben about 8 years
    This mostly wrong. Some attributes has properties defined, so do not. It's really just about how they wrote the spec. It's has nothing to to due with the attributes being standard or not. However, it is true that none-standard properties can only be accessed with getAttribute().
  • Ben
    Ben about 8 years
    This answer is misleading. getAttribute/setAttribute do not deal in literal text. They are just two ways of accessing the same information. Check out my answer below for a complete explanation.
  • jkdev
    jkdev about 8 years
    Thanks for clearing this up. I'm curious, which versions of IE are considered 'modern' and follow the HTML spec?
  • Noitidart
    Noitidart over 7 years
    Thanks for sharing this tomo7, may you please explain a bit more. Does posElem.style = newStyle not work in all browsers (worked for me in Firefox)? Is it just for performance reasons that setAttribute is preferred, avoiding the repaints? Is posElem.style.cssText = newStyle more perfomrant then posElem.style = newStyle?
  • Olgun Kaya
    Olgun Kaya almost 7 years
    I think you run the test on chrome. I teted on my mac using chrome, safari and firefox; expectedly 3 of them showed 3 different results.
  • jgmjgm
    jgmjgm over 6 years
    Are you sure there's not ariaLabel?
  • Suraj Jain
    Suraj Jain about 6 years
    @jkdev IE is never gets modern. What ever it gets to become old.
  • Suraj Jain
    Suraj Jain about 6 years
    Thanks for such an detailed answer, I read a lot about DOM and inheritance like HTMLElement inherit from Element and so on, your answer makes perfect sense.
  • Barzee
    Barzee about 5 years
    You mentioned these four property mappings: dir, id, className, and lang. What about classList? Is classList a property mapping that is guaranteed to exist?
  • ShortFuse
    ShortFuse about 5 years
    classList is 100% guaranteed to exist, but it's not a string property, it's a live DOMTokenList object. Setting .className directly is faster than manipulating classList, but you'd overwriting the whole thing.
  • Daniel Williams
    Daniel Williams about 4 years
    How about .value on <input> and <textarea> tags? What kind are they?
  • ShortFuse
    ShortFuse about 4 years
    The ones mentioned in the answer are what W3C call "reflecting IDL attributes". When you change .value, you are changing the internal value of the HTMLInputElement, which is then reflected on the attributes. They also don't have to be string. .valueAsNumber will change value internally, and it's string form will appear in the value attribute. developer.mozilla.org/en-US/docs/Web/HTML/Attributes
  • Heretic Monkey
    Heretic Monkey almost 4 years
    This has nothing to do with setAttribute vs .attribute; .style is a special property because it's a window into the CSSOM. You simply can't set posElem.style = newStyle, since posElem.style is not a string-valued property. That why the author looks for the cssText property on style, which is a string-valued property of style. The attribute style is string-valued, but is parsed by the engine into the style property.
  • Heretic Monkey
    Heretic Monkey almost 4 years
    JSPerf is dead. Here's a MeasureThat.net test using the hidden Boolean attribute.
  • matty
    matty about 3 years
    The question looks for insight into which of these methods are generally most appropriate to use. Does this answer that question?
  • Uyghur Lives Matter
    Uyghur Lives Matter about 3 years
    @jgmjgm I just tested this on a <select> with an aria-label="...". x.ariaLabel works on Chrome but is undefined on Firefox.
  • S.Serpooshan
    S.Serpooshan over 2 years
    The question is if we shall use element.setAttribute(x, value) or directly as object properties element.x=value