一、前言                                  

  大家先看看下面的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 ; 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 = , 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 ; 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 ; el.detachEvent('onpropertychange', listener);
el.className = el.className.replace(new RegExp('\\b' + cls + '\\b', 'i'), '').trim();
_inner.splice(_inner.indexOf(cls), );
--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')) >= ;
},
toString: function(){
return _inner.toString();
}
}; // 初始化classList[{Number} 索引]获取Attr元素
for (var i = , len = _inner.length; i < len; ++i ){
el.classList[i] = _inner[i];
}
}

由于当原生的add、remove、contains和toggle方法的入参值包含空格时,会抛出InvalidCharacterError,因此在polyfill时也要做相应的检查和抛出异常

// 模拟InvalidCharacterError类
var InvalidCharacterError = function(msg){
this.code = ;
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[] = methodName, msgTpl[] = 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[]; 

        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

JS魔法堂:那些困扰你的DOM集合类型的更多相关文章

  1. JS魔法堂:追忆那些原始的选择器

    一.前言                                                                                                 ...

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

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

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

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

  4. JS魔法堂:LINK元素深入详解

    一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...

  5. JS魔法堂:jsDeferred源码剖析

    一.前言 最近在研究Promises/A+规范及实现,而Promise/A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(<JavaScript框架设计& ...

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

    一.前言 从IE8开始引入了文档兼容模式的概念,作为开发人员的我们可以在开发人员工具中通过“浏览器模式”和“文档模式”(IE11开始改为“浏览器模式”改成更贴切的“用户代理字符串”)品味一番,它的出现 ...

  7. JS魔法堂:不完全国际化&本地化手册 之 理論篇

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

  8. JS魔法堂:不完全国际化&本地化手册 之 实战篇

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

  9. JS魔法堂:IMG元素加载行为详解

    一.前言 在<JS魔法堂:jsDeferred源码剖析>中我们了解到img元素加载失败可以作为函数异步执行的优化方案,本文打算对img元素的加载行为进行更深入的探讨. 二.资源加载的相关属 ...

随机推荐

  1. Linux驱动开发学习笔记(1):LINUX驱动版本的hello world

    1.关于目录    /lib/modules/2.6.9-42.ELsmp/build/   这个是内核源码所在的目录    一般使用这样的命令进入这个目录:cd /lib/modules/$(una ...

  2. U盘安装ubuntu,一直提示start booting from usb device…[转]

    找到U盘中syslinux文件夹下的syslinux.cfg文件,在default vesamenu.c32前面加一个#号就可以了. 我的syslinux.cfg文件修改后如下,够简单吧!!!!建议用 ...

  3. c10k问题及其解决方案

    本文主要讲述高并发http应用中的c10k瓶颈问题:在很多服务器初始状态下,无法服务1w左右的并发连接.这与每次服务的资源消耗.服务器的硬件配置固然有关,但很多时候是被linux的默认配置以及软件st ...

  4. 火狐 SSL 收到了一个弱临时 Diffie-Hellman 密钥的解决办法

    连接 https网址 时发生错误. 在服务器密钥交换握手信息中 SSL 收到了一个弱临时 Diffie-Hellman 密钥. (错误码: ssl_error_weak_server_ephemera ...

  5. Node.js之绝对选择

    几年前,完全放弃Asp.net,彻底脱离微软方向.Web开发,在公司团队中,一概使用Node.js.Mongodb.Git,替换Asp.net mvc.Sql server和Tfs.当时来看,这是高风 ...

  6. 【转】 IOS开发xcode报错之has been modified since the precompiled header was built

    本文转载自  IOS开发xcode报错之has been modified since the precompiled header was built 其实我是升级xcode到4.6.3的时候遇到的 ...

  7. QT在windows下实现截屏操作并保存为png图片

    QPixmap originalPixmap = QPixmap::grabWindow(QApplication::desktop()->winId()); QString format = ...

  8. undercore & Backbone对AMD的支持(Require.js中如何使用undercore & Backbone)

    RequireJS填补了前端模块化开发的空缺,RequireJS遵循AMD(异步模块定义,Asynchronous Module Definition)规范,越来越多的框架支持AMD,像最近的jQue ...

  9. Android界面布局基本属性

    在 android 中我们常用的布局方式有这么几种:1.LinearLayout ( 线性布局 ) :(里面只可以有一个控件,并且不能设计这个控件的位置,控件会放到左上角)              ...

  10. 十五、EnterpriseFrameWork框架核心类库之系统启动入口与初始化

    本章内容是讲三种开发模式,web模式.Winform模式和Wcf模式的系统启动入口有什么区别,以及启动后系统初始化的内容:为什么要把这些单独提出来讲一章,因为我觉得本章非常重要,我们都知道程序中的ma ...