从原型链看DOM--Element类型
Element类型用于表现XML或HTML元素,提供对元素标签名,子节点及特性的访问。原型链的继承关系为 某节点元素.__proto__->(HTML某元素Element.prototype)->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype。
以HTML元素为例:document.documentElement.__proto__->HTMLHtmlElement.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype
Element节点实例有以下特征:以下特征均继承自Node.prototype
- nodeType值为1
- nodeName值为元素标签名
- nodeValue值为null
- parentNode可能是Document或Element
- 其子节点可能是Element,Text,Comment,ProcessingInstruction,CDATASection,EntityReference
要访问元素标签名,可以用nodeName(继承自Node.prototype)属性也可用tagName(继承自Element.prototype)属性,这两个属性会返回相同的值。但注意返回的字符串是大写。在HTML中标签名始终以全部大写表示,而在XML中(有时也包括XHTML)标签名则始终会与源码中的保持一致。假如你不确定自己的脚本将会在HTML还是XML中执行,最好在比较之前进行大小写转化。
document.documentElement.tagName;// "HTML" document.documentElement.nodeName;// "HTML" document.documentElement.nodeName.toLowerCase();// "html"
目录
- HTML元素
- 取得特性
- 设置特性
- attributes属性
- 创建元素
- 元素的子节点
HTML元素
HTML元素的五种标准特性(ele.attributes[index或'属性']或ele.getAttributeNode('属性')得到特性节点),可以取得或修改。
(1).id:继承自Element.prototype,元素在文档中唯一的标识符。 document.body.id;// "Posts"
(2).className:继承自Element.prototype,与元素的class特性对应,即为元素指定的css类。没有将这个属性命名为class,是因为class是ECMAScript的保留字。 document.forms[0].getElementsByTagName('div')[0].className;// "aspNetHidden"
(3).title:继承自HTMLElement.prototype。有关元素的附加说明信息,一般通过工具提示条显示出来。
(4).lang:继承自HTMLElement.prototype。元素内容的语言代码, document.documentElement.lang;// "zh-cn"
(5).dir:继承自HTMLElement.prototype。语言的方向,值为"ltr"(从左至右)或"rtl"(从右至左)。是规定语言内容的文本方向不是文字顺序颠倒。注意一点,应用dir="rtl"后虽然对文本整体是方向性的改变,但对标点符号和文本整体却做了颠倒。其实很好理解,这个属性是规定语言的方向,从右向左读,句号肯定在读的顺序的最后也就是左边。在换行的时候还是从截断的文本整体偏向右侧。
<div id="myID" class="bd" title="body" lang="en" dir="ltr">...</div>
并不是对所有属性的修改都能直观在页面上表现出来。
对id或lang的修改对用户而言是透明不可见的;
对title的修改只会在鼠标移动到这个元素上时才显示出来;
对dir的修改会在属性被重写的那一刻立即影响页面中文本左右对齐方式;
修改className时,如果新类关联了与此前不同的CSS样式那么就会立即应用该样式;
关于了解所有HTML元素以及与之关联的原型类型的构造器可参考高程三P263,有的元素是直接继承自HTMLElement.prototype比如b元素,有的是继承自HTML某元素Element.prototype,比如a元素,它的原型属性指向HTMLAnchorElement.prototype。
(6).attributes:继承自Element.prototype。返回一个NamedNodeMap的实例对象。
这里扩展了解一下NamedNodeMap接口,原型链继承关系为:ele.attributes.__proto__->NamedNodeMap.prototype->Object.prototype。NamedNodeMap接口表示属性节点对象的集合,尽管NamedNodeMap里面的对象可以像数组一样通过索引进行访问但它和NodeList不一样,对象的顺序没有指定。NamedNodeMap集合是即时更新的,因此如果它内部包含的对象发生改变的话,该对象会自动更新到最新状态。
- length:只读,返回映射(map)中对象的数量。
- getNamedItem(str):返回一个给定名字对应的属性节点(Attr)
- setNamedItem(attr):替换或添加一个属性节点到映射map中,会直接反映到DOM中
- removeNamedItem(str):移除一个属性节点,也会即时反映到文档的DOM树中
- item(idx):返回指定索引处的属性节点,当索引超出范围返回null
- getNamedItemNS():根据给定命名空间的参数和name返回一个attr对象
- setNamedItemNS():替换,添加给定命名空间参数和name参数的attr对象
- removeNamedItemNS():移除给定命名空间参数和name参数的attr对象
取得特性
(1).每个元素都有一个或多个特性,这些特性的用途是给相应元素或其内容附加信息。元素继承自Element.prototype上的三个属性,它们的功能是操作特性(不是属性)的方法:
- setAttribute('attr','value')
- getAttribute('attr')
- removeAttribute('attr')
这三个方法都可操作自定义特性,但只有公认的特性才能被应用以属性的形式添加到DOM对象中,以属性方式操作这些特性会被同步到html标签中。HTMLElement的5个特性都有相应属性(意思是Element.prototype或HTMLElement.prototype上的属性可直接通过.形式访问)与其对应:id,title,lang,dir,className。在DOM中以属性方式操作这几个特性会同步到html标签中。因为class特性是这5种特性之一,可以通过className属性访问,xsf特性不在Element.prototype或HTMLElement.prototype对象中有对应属性所以通过属性访问方式获取的值为undefiend。要想访问xsf特性值可以通过getAttribute('xsf')(推荐)或getAttributeNode('xsf').value或attributes["xsf"].value访问。
(2).当然元素还能通过继承HTML某元素Element.prototype上的一些属性,比如input元素的HTMLInputElement.prototype上的disabled可以通过inputele.disabled取得或设置值。inputele.disabled;// false表示该元素未被设置disabled属性即未被禁用,inputele.disabled=true;// 表示为该元素设置不可用属性。
(3).HTML5规范对自定义特性做了增强,只要自定义特性以'data-attrName'形式写到html标签中(setAttribute或直接html代码存在均可),在DOM属性中就可通过ele.dataset.attrName形式访问自定义特性。
dataset属性继自HTMLElement.prototype,它的值是DOMStringMap的实例集合,原型链继承关系为:ele.dataset.__proto__->DOMStringMap.prototype->Object.prototype。
(4).特性名是不区分大小写的,getAttribute('id')和getAttribute('ID')都代表同一个特性。
介绍两个特殊的特性:它们虽然有对应的属性名,但属性的值与通过getAttribute()返回的值并不相同。style属性继承自HTMLElement.prototype,on事件处理属性继承自HTMLElement.prototype或Element.prototype。
(a).style:用于通过css为元素指定样式。
通过getAttribute()访问返回的style特性值(在标签中定义的)中包含CSS文本
通过style属性访问返回一个CSSStyleDeclaration类型集合对象,由于style属性是用于以编程方式访问访问元素样式的因此并没有直接映射到style特性。该css属性来自标签中被设置的style特性。
并没有background特性值,可以看到不论通过哪个方式获得的结果都只有元素上style特性设置的属性才会出现。
通过style属性返回了一个CSSStyleDeclaration类型实例集合,原型链继承关系为:div.style.__proto__->CSSStyleDeclaration.prototype->Object.prototype
获得的集合中的属性只有已设置的才有值,其余的属性为空字符串即使它们都有默认值。
简单学习下CSSStyleDeclaration接口:代表css键值对的集合,它在一些API中被使用
- HTMLele.style 用于操作单个元素的样式(<ele style="...">)
- 在getComputedStyle中应用:CSSStyleDeclaration是window.getComputedStyle()返回的只读接口,其中每个键都有值,或被设置的值或默认的值。
ele.style.cssText:声明块的文本内容,修改这个属性会直接将标签中的style特性内容改变。
ele.style.length:属性的数量即有具体值的css声明的数量。window.getComputedStyle()返回值为262。
ele.style.parentRule:包含的CSSRule;
ele.style.getPropertyPriority('attr'):返回可选的优先级
ele.style.getPropertyValue('attr'):返回属性值
ele.style.item(idx):返回属性名,有具体的值的返回属性名,没有具体值的返回""
ele.style.removeProperty():删除的属性,会直接反映到HTML文档中元素style特性节点。返回""
ele.style.setProperty('attr','value','priority'):设置属性,eg: document.body.style.setProperty('color','red','important')
(b).on事件属性:以onclick为例,在元素上使用时,onclick特性中包含的是JavaScript代码,但通过getAttribute()访问返回相应代码的字符串。在访问onclick属性时会返回一个JavaScript函数(当onclick属性上不存在函数对象且未在元素标签中指定相应特性(为什么说不是属性是因为如果在ele上没有找到onclick属性那就去标签中找onclick特性值)返回null)。由于存在这些差异,在通过JavaScript以编程方式操作DOM时建议使用onclick属性值,只有在取得自定义特性值的情况下才通过getAttribute()访问。
当一个元素标签中既有onclick特性,同时给ele.onclick指定函数(这操作并不会影响原来标签中onclick特性的值),则最后只执行ele.onclick属性的函数。
并且通过getAttribute访问仍得到的是标签上的特性值,且即使之前已经给onclick属性赋值了但控制台显示元素自身并没有这个属性。我不明白为什么document.body自身上会没有onclick属性,那当访问document.body.onclick时候去哪访问onclick的值,按着原型链吗?如果函数是在原型链上的onclick属性上也不应该啊HTMLElement.prototype.onclick=function(){console.log(2)}这样不就使这个方法成共享的了任何HTML元素实例都能访问,这显然不实际因为我们只想为某一元素设置某事件函数。对于document.body自身上会没有onclick属性不知道是不是JS引擎内部实现的,如果是那具体是怎么实现的?知道的盆友麻烦告知~
document.body.getAttribute('onclick');// "(function(){console.log(1)})()" document.body.onclick;// function (){ console.log(2) } document.body.hasOwnProperty('onclick');// false
(c).在<=IE7通过getAttribute()方法访问style特性和onclick这样的事件处理程序特性时,返回的值与属性的值相同。即getAttribute('style')返回的不是字符串而是对象,getAttribute('onclick')返回的不是字符串而是函数。虽然IE8已修复该bug但不同版本的不一致性还是建议使用属性访问HTML特性。
一道面试题:下列关于IE,FF下面脚本的区别描述错误的是:感觉这道怪怪的考的是早期支持情况??
A.innerText IE支持,FF不支持 FF早期不支持
B.document.createElement FF支持,IE不支持 X
C.setAttribute('class','styleclass')FF支持,IE不支持 IE早期不支持
D.用setAttribute设置事件FF不支持,IE支持 X
IE:all支持innerText >IE8支持setAttribute设置特性或事件
FF:新版本支持,旧版本不支持outerHTML outerText innerText;setAttribute支持
设置特性
setAttribute('attr','value'):继承自Element.prototype。参数为要设置的特性名和值,如果特性已经存在,setAttribute()会以指定值替换现有值,如果特性不存在,setAttribute会创建该属性并设置相应值。
通过该方法可以操作HTML特性也可以操作自定义特性,通过这个方法设置的特性名会被统一转换为小写形式即"ID"转换为"id"。
因为所有特性都是属性,所以直接给属性赋值就可以设置特性的值,但通过添加自定义属性并不会成为该元素的特性这样实际上为元素自身添加了属性。
document.body.id="test"; document.body.getAttribute('id');// "test" document.body.hasOwnProperty('id');//false document.body.myid="test"; document.body.getAttribute("myid");// null document.body.hasOwnProperty('myid');//true
注:<=IE7中,setAtttribute()存在一些异常行为不但通过setAttribute()设置元素基本特性或事件特性没用而且通过点赋值法设置元素属性也不会反应到元素标签中。尽管到IE8才解决这个bug,但还是推荐用点赋值法设置特性。
removeAttribute():继承自Element.prototype,可以彻底删除元素特性,不仅会清除特性值还会从元素中完全删除特性。该方法不常用,但在序列化DOM元素时,可以通过它来确切指定要包含哪些特性。
attributes属性
Element类型是使用attributes属性的唯一一个DOM节点类型,attributes属性继承自Element.prototype。它的值是NamedNodeMap类型实例,也是个动态集合,元素的每一个特性都由一个Attr类型节点表示,每个节点都保存在NamedNodeMap对象中。
Object.getOwnPropertyNames(NamedNodeMap.prototype);// ["length", "item", "getNamedItem", "getNamedItemNS", "setNamedItem", "setNamedItemNS", "removeNamedItem", "removeNamedItemNS", "constructor"]
(1).getNamedItem(name):返回nodeName属性等于name的特性节点。
document.body.attributes.getNamedItem("id");// id="test" document.body.attributes[0].nodeName;// "id"
(2).removeNamedItem(name):从列表中移除nodeName等于name的节点。该方法与在元素上调用removeAttribute()方法效果相同,直接删除具有给定名称的特性节点。这两种方法唯一的区别就是返回值,removeNamedItem返回被删除特性的Attr节点。
attrMap.removeNamedItem('style');// style="overflow: hidden;" document.body.removeAttribute('class');// undefined
(3).setNamedItem(attrnode):向列表中添加属性节点,以节点的nodeName属性为索引。
var attr=document.createAttribute('class'); attr.value="as"; attr;// class="as" document.body.attributes.setNamedItem(attr); document.body.attributes;//NamedNodeMap {0: id, 1: style, 2: aria-hidden, 3: class, length: 4}
(4).item(pos):返回位于数字pos位置处的特性节点。
document.body.attributes.item(3);// class="as"
attributes属性中包含一系列节点,如果想要遍历元素特性attributes是个很好的选择。每个节点的nodeName值就是特性节点的名称,节点的nodeValue值就是特性的值。
document.body.attributes.getNamedItem("id").nodeValue;// "test" document.body.attributes["id"].nodeValue;// "test"
设置特性的值:先取得特性节点然后将其nodeValue设置为新值。
当你设置document.body.id="test"时候JS引擎内部可能完成了如下操作
document.body.id="newid"; transAttr(document.body,'id'); function transAttr(ele,id){ var attrMap=ele.attributes; for(var i in attrMap){ if(attrMap.hasOwnProperty(i)){ if(attrMap[i].nodeName=='id'){ attrMap[i].nodeValue=ele.id; } } } delete document.body.id; }
在需要将DOM结构序列化为XML或HTML字符串时,多数都会涉及遍历元素特性。以下代码展示了如何迭代元素的每一个特性然后将它们构造成name="value"这样的键值对形式
function outputAttributes(ele){ var pairs=new Array(), attrName,attrValue,i,len; for(i=0;i<ele.attributes.length;i++){ attrName=ele.attributes[i].nodeName; attrValue=ele.attributes[i].nodeValue; pairs.push(attrName+"=\""+attrValue+"\""); } return pairs.join(" "); }
outputAttributes(document.body);// "id="cd" aria-hidden="true""
这个函数使用了一个数组来保存名值对,最后再以空格分隔符将它们拼接起来(这是序列化长字符串时的常用技巧)。
- 针对attributes中的特性,不同浏览器返回顺序不同,这些特性在XML或HTML代码中出现的先后顺序不一定与它们出现在attributes对象中的顺序一致。
- <=IE7会返回HTML元素所有可能特性,包括没有指定的特性。针对IE7这一bug可以改进一下程序,每个特性节点都有一个specified(继承自Attr.prototype)的属性,specified=true表明要么是在HTML元素中指定了相应特性要么通过setAttribute()方法设置了该特性。在<=IE7中不论有没有设置过某特性,某特性都有specified值,但被设置过的值为true,所有未设置过的特性该属性都为false。在其他浏览器中不会为这类特性生成对应的特性节点(因此在这些浏览器中任何特性节点的specified值始终为true)
当并没有为document.body设置特性节点class //<=IE7 document.body.attributes["class"].specified;// false //IE10 document.body.attributes["class"];// undefiend
改进后的代码为:
function outputAttributes(ele){ var pairs=new Array(), attrName,attrValue,i,len; for(i=0,len=ele.attributes.length;i<len&&ele.attributes[i].specified==true;i++){ attrName=ele.attributes[i].nodeName; attrValue=ele.attributes[i].nodeValue; pairs.push(attrName+"=\""+attrValue+"\"") } return pairs.join(" "); }
创建元素
document.createElement():继承自Document.prototype,参数为标签名,这个标签名在HTML文档中不区分大小,在XML(包括XHTML)文档中是区分大小写的。在使用document.createElement创建新元素的同时,也为新元素设置了ownerDocument(继承自Node.protoype)属性,此时还可操作元素的特性为它添加更多子节点以及执行其他操作。
var div=document.createElement('div'); div.id="myNewid";// "myNewid" div.className="box";// "box"
在新元素上设置这些特性只是给它们赋予了相应信息,由于新元素尚未被添加到文档树中,因此设置这些特性不会影响浏览器显示。要把新元素添加到文档树中,可使用appendChild(),insertBefore(),replaceChild()均继承自Node.prototype。一旦将元素添加到文档树,浏览器就会立即呈现该元素。此后对这个元素所做的任何修改都会实时反映在浏览器中。
在<=IE8中以另一种方式使用createElement,即为这个方法传入完整的元素标签也可以包含属性,document.createElement('<div id="test"></div>') 。这种方式有助于避开在IE7及更早版本中动态创建元素(document.createElement('div')然后插入叫动态创建)的某些问题,之前存在以下这些问题:
- 不能设置动态创建的<iframe>元素的name特性
- 不能通过表单的reset()方法重设动态创建的<input>元素。
- 动态创建的type特性值为"reset"的<button>元素重设不了表单。
- 动态创建的一批name相同的单选按钮彼此毫无关系,name值相同的一组单选按钮本来应该用于表示同一选项的不同值,但动态创建的一批这种单选按钮之间却没有这种关系。
上述所有问题都可通过在createElement()中指定完整的HTML标签来解决。
if(window.navigator.userAgent.search(/MSIE([^;]+)/)){ //创建一个带name特性的iframe标签 var iframe=document.createElement('<iframe name="myframe"></iframe>'); //创建input元素 var input=document.createElement('<input type="checkbox">'); //创建button元素 var button=document.createElement('<button type="reset"></button>') ; //创建一个单选按钮 var radio1=document.createElement('<input type="radio" name="choice" value="one">'); var radio2=document.createElement('<inpur type="radio" name="choice" value="two">'); }
元素的子节点
元素的childNodes属性(继承自Node.prototype)包含了它所有子节点,这些子节点可能是元素,文本节点,注释,处理指令。不同浏览器在看待这些节点方面存在不同。
<ul id="myList"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul>
IE:
IE9~11
IE5~8
Chrome46.0.2490.80:
FF44.0.2 :
如果需要通过childNodes属性遍历子节点,通常要先检查一下当前节点的nodeType属性。
var ul=document.getElementById('myList'); for(var i=0;i<ul.childNodes.length;i++){ if(ul.childNodes[i].nodeType==1){ //do else } }
如果想通过标签名取得子节点或后代节点,元素也支持getElementsByTagName()(继承自Element.prototype),返回HTMLCollection类型实例集合是返回当前元素的后代(如果有多层嵌套的话包括子元素和子元素的后代)。document.getElementsByTagName()是继承自Document.prototype。
参考
《JavaScript高级程序设计》
反本求源——DOM元素的特性与属性
MDN NamedNodeMap
MDN CSSStyleDeclaration