JS魔法堂:那些搅扰你的DOM鸠合范例

一、媒介                           

人人先看看下面的js,猜猜效果会如何吧!

可选答案:

①. 猎取id属性值为id的节点元素

②. 抛namedItem is undefined的非常

var nodes = document.getElementsByName('dummyName');
var node = nodes.namedItem('id');

  答案是两种都有能够哦!document.getElementsByName在Chrome和FF30.0中返回NodeList(木有namedItem要领的),在IE全系列中都返回HTMLCollection,吐血了吧?

  DOM鸠合又何止这些呢,下面我们就一起来讨论一下吧!

二、搅扰你我的NodeList与HTMLCollection

雷同点:

1. 类数组。有length属性,能够用下标索引来接见个中的元素,但没有Array的slice等要领;

2. 只读。没法增删个中的元素;

3. 及时同步DOM树的变化。若DOM树有新元素到场,该范例的对象也会将新元素包括进来;

4. 可经由过程下标数字范例索引猎取鸠合中指定位置的元素;

5. 可经由过程item({String | Number} 索引)要领猎取鸠合中指定位置的元素,若经由过程索引找不到元素,则以第一个元素作为返回值。

差别点(重要表如今HTMLCollection比NodeList才能更壮大):

1. HTMLCollection对象可经由过程namedItem({String} id或name)猎取首个婚配的元素,若没有则返回null;

2. HTMLCollection对象可经由过程点体式格局猎取第个id或name婚配的元素,若没有则返回undefined。

各浏览器选择器返回范例差异:

// IE678 返回具有HTMLCollection特征(有namedItem要领)的[object Object]对象
// IE9、10、11、FF、Chrome均返回HTMLCollection
document.images;
document.links;
document.anchors;
document.forms;
document.embeds;
document.scripts;
document.applets;
document.plugins;
Node对象.getElementsByTagName;
Node对象.getElementsByTagNameNS;
Node对象.getElementsByClassName;
HTMLTableElement对象.tBodies;
HTMLTableElement对象.children;
HTMLTableElement对象.rows;
HTMLTableRowElement对象.cells;
HTMLMapElement对象.areas;


// IE678 返回具有HTMLCollection特征(有namedItem要领)的[object Object]对象
// IE9、10、11返回HTMLCollection
// FF30.0、Chrome返回NodeList
document.getElementsByName;

// IE678 返回具有NodeList特征(无namedItem要领)的[object Object]对象
// IE9、10、11、FF、Chrome均返回NodeList
Node对象.childNodes;

// IE5678 返回具有HTMLCollection特征(有namedItem要领)的[object Object]对象
// IE9、10返回[object HTMLCollection]
// IE11、Chrome返回[object HTMLAllCollection]
// FF30.0返回[object HTML document.all class]
document.all;
  1. 整体来讲Chrome的完成更靠近W3C范例;
  2. HTMLAllCollection、HTMLCollection和[object HTML document.all class]功用没什么区分,只是范例差别罢了;
  3. 由于document.getElementsByName在差别的浏览器中返回差别范例的对象,因而引荐运用[{Number} 索引]的要领来接见鸠合元素会费心一些;
  4. 题外话:children属性仅猎取nodeType为1的元素,而childNodes会将一切子元素的包括进来;
  5. 注重:IE9、10、11的HTMLCollection与其他浏览器的HTMLCollection可不雷同哦,详细请看下一节吧!

三、同名差别性——IE下奇异的HTMLCollection 

  如果人人看过《JS魔法堂:追想那些原始的选择器》,应当会相识到在IE5678下,document.all会返回一个类函数对象,也就是上文说到的带有HTMLCollection特征的[object Object]对象。实在IE这一传统一向延续到IE11,这就致使IE9、10、11下的HTMLCollection与W3C规范涌现同名而差别性子的问题了。

  作甚类函数?

纯属本人擅自定义罢了,用于指那些具有函数的特征,但instanceof Function却返回false的对象。

真心想对IE说一句,你这么吊,你妈妈晓得吗?

四、StaticNodeList——伪装成NodeList的小子          

  从IE8最先就多了个querySelectorAll选择器要领。详细行动以下:

// IE8返回 [object Object], IE9+和chrome、FF就返回[object NodeList]
var nodes =  document.querySelectorAll('*');

// IE8返回 空鸠合[object Object],IE9+和chrome、FF就抛至少是1个函数入参的非常
nodes = document.querySelectorAll();

// 各浏览器均抛SyntaxError非常
nodes = document.querySelectorAll('') 或 document.querySelectorAll(非字符串范例入参);

人人不要被浏览器返回的NodeList所蒙骗,实在querySelectorAll返回的是StaticNodeList对象。其特征与NodeList基础无异,唯一的区分就是StaticNodeList是不会及时同步DOM树变化,因而在polyfill querySelectorAll的时刻就不必斟酌及时同步DOM树变化的问题了。

五、HTMLOptionsCollection——HTMLCollection的子类     

  HTMLSelectElement对象.options会返回一个HTMLOptionsCollection鸠合对象,鸠合内存储HTMLOptionElement范例的元素。HTMLOptionsCollection范例除了父类HTMLCollection的特征外,另有以下成员要领、属性可用。

add({HTMLOptionElement} opt[, {HTMLOption | Number} before]); // 将选项元素到场到鸠合的末了,或指定的元素(位置)的背面
remove({Number} index);// 删除指定位置的选项
selectedIndex; // 当前选中项的索引,从0最先

六、HTMLFormControllersCollection——HTMLCollection的子类

  HTMLFormElement对象.elements会返回一个HTMLFormControllersCollection鸠合对象,鸠合内存储种种表单元素。它特别之处是经由过程点属性猎取id或name婚配的元素时,平常的HTMLCollection鸠合对象在纵然有多个婚配的元素的情况下,仅返回首个婚配的元素;而HTMLFormControllersCollection,在有一个婚配的元素时就返回该元素,如有多个婚配的元素则返回一个RadioNodeList鸠合对象。

              

七、RadioNodeList——NodeList的子类

  初看RadioNodeList很有能够认为鸠合元素就是单选表单元素,实在RadioNodeList能够存储恣意范例的表单元素。不过其value属性就值显现个中被选中的单选项表单元素的value值,若没有单选项表单元素,或没有选中单选项表单元素,那末value值为空字符串。

八、HTMLAllCollection——HTMLCollection的子类        

  IE11、Chrome最先,document.all将返回HTMLCollection子类HTMLAllCollection的对象,其行动特征和HTMLCollection一致。但IE11中的HTMLAllCollection还能够看成函数运用,详细请看本文的第三节。

九、NamedNodeMap——无序Attr元素鸠合    

  HTMLElement对象.attributes会返回NamedNodeMap鸠合对象,内部保留的是[object Attr]范例的对象。NamedNodeMap和HTMLCollection、NodeList差别,由于它是无序鸠合,虽然能够经由过程数字范例的下标索引接见NamedNodeMap鸠合中的元素,但该索引值并不实在代表元素在鸠合中的位置。下面是NamedNodeMap的成员要领:

[{String} 属性名]
item({Number | String} 索引)
getNamedItem(); //经由过程称号返回指定的属性节点
getNamedItemNS(); //经由过程称号和定名空间返回指定的属性节点
setNamedItem(); //经由过程称号设置指定的属性节点
setNamedItemNS(); //经由过程称号和定名空间设置指定的属性节点
removeNamedItem(); //经由过程称号删除指定的属性节点
removeNamedItemNS(); //经由过程称号和定名空间删除指定的属性节点

注重:HTMLElement对象.attributes仅返回显现属性(简朴地说就是直接写在html标签上的属性,或经由过程setAttribute设置的属性,详细请看《JS魔法堂:不要再被Attribute和Property搅扰我们了》)

十、DOMTokenList——HTML5新特征classList的范例哦!   

  用过classList的都晓得它大大提高了我们设置css类的效力,但IE10以下却不支撑,polyfill能够帮我们一把。但在polyfill前,我们应当先相识清晰classList的范例DOMTokenList的特征。

  1. 只读

  2. 及时同步响应元素的className属性值的变化

  3. 具有以下要领和属性

 {Undefined} add({String} class); // 已存在的类不会被反复增加
 {Undefined}  remove({String} class)
 {Undefined}  toggle({String} class)
 {Boolean} contains({String} class); //搜检是不是有指定的类
 item({Number} 索引); //经由过程索引猎取指定位置的类
 length; //示意类的个数
 // 没法经由过程[{Number} 索引]的体式格局来设置类,只能经由过程该体式格局来猎取类

  那末如今我们就着手polyfill吧,注重难点在及时同步这一块,解决办法就是用onpropertychange来监听className的变化(想相识更多,请看《JS魔法堂:DOM天下的观察者》)

function polyfillClassList(el){
  var r = /\s+/, cls = el.className, _inner  =  cls ? cls.trim().split(r) : [];
  var listener = function(e){
    if (e.propertyName !== 'className') return void 0;

    var cLst = el.classList, oLen = _inner.length, cls=  el.className;
    _inner  =  cls ? cls.trim().split(r) : [];
    var len = (cLst.length = _inner.length);
    for (var i = 0, maxLen = Math.max(oLen, len); i < maxLen; ++i){
       if (i < len){
           cLst[i] = _inner[i])
       } else {
           delete cLst[i];
       }
    }    
  };
  el.attachEvent('onpropertychange', listener);
  el.classList = {
     length: _inner.length,
     item: function(index){
        return _inner[index] || null;
     },
     add: function(cls){
       // 省略搜检cls值是不是有用的代码
       if (this.contains(cls)) return void 0;

       el.detachEvent('onpropertychange',  listener);
       el.className += ' ' + cls;
       _inner.push(cls);
       this[this.length++] = cls;
       el.attachEvent('onpropertychage', listener);
     },
     remove: function(cls){
       // 省略搜检cls值是不是有用的代码
       if (!this.contains(cls)) return void 0;

       el.detachEvent('onpropertychange',  listener);
       el.className = el.className.replace(new RegExp('\\b' + cls + '\\b', 'i'), '').trim();
       _inner.splice(_inner.indexOf(cls), 1);
       --this.length;
       el.attachEvent('onpropertychage', listener);
     },
     toggle: function(cls){
       // 省略搜检cls值是不是有用的代码
       this[this.contains(cls) ? 'remove' : 'add'](cls);
     },
     contains: function(cls){
       // 省略搜检cls值是不是有用的代码
       return el.className.search(new RegExp('\\b' + cls + '\\b', 'i')) >= 0;
     },
     toString: function(){
       return _inner.toString();
     }
  };  

  // 初始化classList[{Number} 索引]猎取Attr元素
  for (var i = 0, len = _inner.length; i < len; ++i ){
    el.classList[i] = _inner[i];
  }
}        

由于当原生的add、remove、contains和toggle要领的入参值包括空格时,会抛出InvalidCharacterError,因而在polyfill时也要做响应的搜检和抛出非常

// 模仿InvalidCharacterError类
var InvalidCharacterError =  function(msg){
   this.code = 5;
   this.message = msg;
   this.name = 'InvalidCharacterError';
};
InvalidCharacterError.prototype = DOMException;


// 搜检入参并抛非常
// @param {String} methodName add、remove等要领名
// @param {String} cls css类
var check = function(methodName, cls){
  var msgTpl = ["Failed to execute '", , "' on 'DOMTokenList': The token provided ('", ,"') contains HTML space characters, which are not valid in tokens."];
  if (/\s+/.test(cls)){
     throw new InvalidCharacterError((msgTpl[1] = methodName, msgTpl[3] = cls, msgTpl).join(''));
  }
};

更多关于非常处置惩罚、Error和Exception的信息请注意《JS魔法堂:非常处置惩罚并不那末简朴》

十一、DOMStringMap范例——HTML5新特征dataset的范例哦!  

  IE11最先支撑 HTML5 JS API的dataset,它是就特地用来操纵自定义特征(custom attribute,属性的分类请看《JS魔法堂:特征、属性,傻傻分不清晰》)的对象,其范例为DOMStringMap,从称号可知其为字符串字典。下面连系dataset申明其特性吧,详细以下:

  ①. dataset针对以”data-“开首的自定义特征操纵;

  ②. 经由过程形如dataset.rawData猎取data-raw-data的属性值;

  ③. 经由过程形如dataset.rawData = ‘hello world!’给data-raw-data的属性赋值;

  ④. 经由过程形如delete dataset.rawData删除属性data-raw-data;

  ⑤. 经由过程for in 遍历dataset的属性;

  ⑥. 属性值必需或将自动转换为String范例;

  ⑦. 实在它就是除了setAttribute、getAttribute等操纵自定义特征的另一个接口罢了,而且效力比get/setAttribute低,但大大简化操纵代码。

  别的,JQuery中也有一个data函数,那末它跟以”data-“开首的自定义特征有什么关联呢?

html:<div id="div" data-raw="raw"></div>,运用jquery-1.10.2

        var $el = $('#div'), el = $el[0]; 

        function log(){
            console.log($el.data('raw'));
            console.log(el.dataset['raw']);
            console.log(el.outerHTML);
        }
        log();
     // 输出:
     // raw
     // raw
        // 

<div id="div" data-raw="raw"></div>



        $el.data('raw', '$');
        log();
        $el.data('raw', 'raw');
     // 输出:
     // $
     // raw
     // 

<div id="div" data-raw="raw"></div>




        el.dataset.raw = 'dataset';
        log();
        el.dataset.raw = 'raw';
     // 输出:
     // raw
     // dataset
     // 

<div id="div" data-raw="dataset"></div>




        delete el.dataset.raw;
        log();
        // 输出:
        // raw
        // undefined
        // <div id="div"></div>

        el.dataset.newRaw = 'newRaw';
        console.log($el.data('newRaw')); // 输出newRaw

  从上面的实例可知:

    挪用JQuery的data函数接见属性时,它会在库内部的特征映射表中寻觅同属性名的键值对,没有则采用与dataset雷同的体式格局猎取属性值,若胜利则将在特征映射表中新建一个键值对,然后后续的接见和赋值操纵均仅仅针对该键值对。赋值操纵时,仅仅在特征映射表中新建键值对,并不会赋值到标签对应的”data-*”特征中。

    为什么JQuery要设想成如许呢?由于dataset的自定义特征值必需为String范例,给予其他范例时会发作隐式范例转换,不便于暂存对象、数组等数据。JQuery这类算是折衷的做法吧,所以用JQuery的data API操纵自定义特征时最好不要跟dataset或get/setAttribute等原生API混合用咯。

  本节参考:《HTML5自定义属性对象Dataset 简介》

十二、 总结                        

实在DOM的鸠合又何止上述的这些呢,在后续的日子里我会边进修边完美本文的,感谢收看!

如果您以为本文的内容风趣就扫一下吧!捐赠互勉!
《JS魔法堂:那些搅扰你的DOM鸠合范例》

    原文作者:肥仔John
    原文地址: https://segmentfault.com/a/1190000003061889
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞