一、前言                                  

  大家先看看下面的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的集合又何止上述的这些呢,在后续的日子里我会边学习边完善本文的,谢谢收看!

尊重原创,转载时请注明来自:http://www.cnblogs.com/fsjohnhuang/p/3819165.html ^_^肥仔John

如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!

本文转自^_^肥仔John博客园博客,原文链接:http://www.cnblogs.com/fsjohnhuang/p/3819165.html,如需转载请自行联系原作者

JS魔法堂:那些困扰你的DOM集合类型相关推荐

  1. JS魔法堂:属性、特性,傻傻分不清楚

    一.前言 或许你和我一样都曾经被下面的代码所困扰 var el = document.getElementById('dummy'); el.hello = "test"; con ...

  2. JS魔法堂:判断节点位置关系

    一.前言 在polyfill querySelectorAll 和写弹出窗时都需要判断两个节点间的位置关系,通过jQuery我们可以轻松搞定,但原生JS呢?下面我将整理各种判断方法,以供日后查阅. 二 ...

  3. JS魔法堂:浏览器模式和文档模式怎么玩?

    一.前言 从IE8开始引入了文档兼容模式的概念,作为开发人员的我们可以在开发人员工具中通过"浏览器模式"和"文档模式"(IE11开始改为"浏览器模式& ...

  4. JS魔法堂:不完全国际化本地化手册 之 拓展篇

    前言  最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...

  5. JS魔法堂:doctype我们应该了解的基础知识

    一.前言 什么是doctype?其实我们一直使用,却很少停下来看清楚它到底是什么,对网页有什么作用.本篇将和大家一起探讨那个默默无闻的doctype吧! 二.什么是doctype doctype或DT ...

  6. JS魔法堂:初探传说中的setImmediate函数

    一.前言   由于JavaScript程序为单线程,因此在执行长时间的操作时(如循环和递归操作)到导致UI线程长期被阻塞,无法响应用户操作请求(如点击按钮等),让用户体验大打折扣.于是想到将一个长时间 ...

  7. JS魔法堂:IE5~9的DragDrop API

    一.前言      < HTML5魔法堂:全面理解Drag & Drop API>中提到从IE5开始已经支持DnD API,但IE5~9与HTML5的API有所不同,下面我们来了解 ...

  8. JS魔法堂:mmDeferred源码剖析

    一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...

  9. JS魔法堂:彻底理解0.1 + 0.2 === 0.30000000000000004的背后

    Brief 一天有个朋友问我"JS中计算0.7 * 180怎么会等于125.99999999998,坑也太多了吧!"那时我猜测是二进制表示数值时发生round-off error所 ...

最新文章

  1. html自动轮播中间放大,html5+css如何实现中间大两头小的轮播效果
  2. iPhone中的heic格式怎么查看,heic文件怎么打开
  3. BUUCTF(pwn)hgame2018_flag_server(简单的栈溢出)
  4. maven 遇到failOnMissingWebXml有关问题解决方法
  5. hibernate脏数据_Hibernate性能提示:脏收集效果
  6. 机器学习笔记(2):单变量线性回归
  7. Wireshark-001基本设置
  8. 赖世雄英语学习三原则
  9. 电阻触摸屏和电容触摸屏你更喜欢哪一个?
  10. vue element-ui Radio单选框默认值选不中的原因:混用字符和数字
  11. SQLite解决插入大量数据速度慢的问题
  12. 2019年3月菜鸟实习生电话面试题
  13. 天源财富:型催化体系可实现高效电催化析氢
  14. 成都信息工程大学计算机网络技术题库,2017年成都信息工程大学计算机学院341农业知识综合三[专业硕士]之计算机网络考研题库...
  15. 前端调用后端接口全都报403,但是换个浏览器可以正常访问,请问有大佬知道什么问题吗?
  16. 张丹带你用R语言开始量化投资
  17. Android子系统
  18. MySQL字符串处理函数的用法及使用举例
  19. python学习笔记(持续更新)
  20. 关于矩阵胶囊与EM路由的理解(基于Hinton的胶囊网络)

热门文章

  1. 【C++】C++中substr的用法
  2. Oracle数据库间的数据复制 - SQLPlus中的COPY命令
  3. list集合去除重复对象
  4. window7 已经分好区的硬盘如何再次分区?
  5. Flask-----轻量级的框架,快速的搭建程序
  6. vue2.0:(六)、移动端像素border的实现和整合引入less文件
  7. 北京公交“十三五”将通过大数据实现线路优化
  8. 软考网络工程师--知识产权与标准化
  9. Android中 requestCode与resultCode的区别与用法
  10. HTTPS 使用成本