When to use setAttribute vs .attribute= in JavaScript?

后端 未结 9 2212
闹比i
闹比i 2020-11-22 11:44

Has a best-practice around using setAttribute instead of the dot (.) attribute notation been developed?

E.g.:

myObj.setAttr         


        
相关标签:
9条回答
  • 2020-11-22 12:19

    This looks like one case where it is better to use setAttribute:

    Dev.Opera — Efficient JavaScript

    var posElem = document.getElementById('animation');
    var newStyle = 'background: ' + newBack + ';' +
    'color: ' + newColor + ';' +
        'border: ' + newBorder + ';';
    if(typeof(posElem.style.cssText) != 'undefined') {
        posElem.style.cssText = newStyle;
    } else {
        posElem.setAttribute('style', newStyle);
    }
    
    0 讨论(0)
  • 2020-11-22 12:30

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

    0 讨论(0)
  • 2020-11-22 12:31

    Interesting takeout from Google API script regarding this:

    They do it like this:

    var scriptElement = document.createElement("script");
    scriptElement = setAttribute("src", "https://some.com");
    scriptElement = setAttribute("nonce", "https://some.com");
    scriptElement.async = "true";
    

    Notice, how they use setAttribute for "src" and "nonce", but then .async = ... for "async" attribute.

    I'm not 100% sure, but probably that's because "async" is only supported on browsers that support direct .attr = assignment. So, there's no sense trying to sestAttribute("async") because if browser doesn't understand .async=... - it will not understand "async" attribute.

    Hopefully, that's a helpful insight from my ongoing "Un-minify GAPI" research project. Correct me if I'm wrong.

    0 讨论(0)
  • 2020-11-22 12:36

    methods for setting attributes(for example class) on an element: 1. el.className = string 2. el.setAttribute('class',string) 3. el.attributes.setNamedItem(object) 4. el.setAttributeNode(node)

    I have made a simple benchmark test (here)

    and it seems that setAttributeNode is about 3 times faster then using setAttribute.

    so if performance is an issue - use "setAttributeNode"

    0 讨论(0)
  • 2020-11-22 12:38

    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
    
    0 讨论(0)
  • 2020-11-22 12:38

    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"
    
    0 讨论(0)
提交回复
热议问题