序
- 之前提到过 DHTML->DOM
- 本章介绍与浏览器相关的DOM节点以及JS对DOM的实现
- 注意到,IE中的DOM都是通过COM实现的,与一般的DOM的运行机制有差异(考虑能力检测、浏览器兼容)
- 节点层次
- DOM操作技术:动态脚本、动态样式、表格<table>的操作、NodeList相关注意事项。go
节点层次
- DOM将HTML或者XML文件映射成节点树。从前了解的节点类型大致上分为3种(元素节点、属性节点和文档节点),然而本书中进一步细分为12种类型,继承自一个基类型。
- 每个节点实际上是一个节点对象,可以通过获取得到,并调用相关属性和方法进行处理。
- DOM实际上是一套API,提供了各种接口(节点类型接口)。
- Node类型(一些通用的方法和属性)
- 存储了一些节点的通用方法,其他类型的节点都继承了这个节点。但是需要注意IE8及之前的版本并不具备这个对象类型。
- nodeType属性。存储了一些常量,表示节点类型。
- nodeName和nodeValue属性:顾名思义。并不是所有节点的这两个属性有值,有时是null
- 节点关系:构成了节点树,访问其他节点的方法或者属性:
- childNodes属性:动态的NodeList,是类数组对象,具有length。可以通过Array.prototype.slice()转换为数组。注意IE8及之前的处理需要用遍历的方法处理。
- parentNode属性
- previousSibling属性、nextSibling属性
- firstChild和lastChild属性
- hasChildNodes()方法
- ownerDocument :指向DOM节点
- 有些节点并不存在子节点,这里会存在差异。
- 操作节点:上述关系节点是只读的,可以通过操作节点进行一些操作。
- appendChild():注意到如果传入的节点已经是文档的一部分了,那么这个节点会转移到新的位置(指针的改变)。
- DOM中的节点不会同时出现在两个或者多个DOM中。
- insertBefore(newNode, null/oldNode)
- replaceChild(newNode, waitToReplacedNode)
- 被代替的节点仍然存在在文档中,只不过在文档中没了它的位置:各种指针关系已经不存在(或者被替代了)。无法通过其他节点访问到它,所以说它没有自己的位置。
- removeChild():只是移除
- 这些操作是在“关系”的基础上:主要因为DOM通过“关系”(指针)来访问节点。
- 其他方法:
- cloneNode():只复制特性,不复制其他操作(如事件处理)。IE这里存在bug,建议复制之前,移除事件处理程序。
- cloneNode(true):克隆节点以及其下完整的其他节点
- cloneNode(false):只复制该节点,需要借助appendChild()等将其加入到文档中。
- normalize():处理异常的文档节点(删除空白文档节点、合并相邻的文档节点)
- Document类型
- document与html的关系:document是HTMLDocument(继承自Document类型)的一个实例。也是window对象的属性。
- Document节点特征:
- nodeType为9
- nodeName为"#document"
- nodeValue为null
- parentNode为null
- ownerDocument为null
- 其子节点可能是DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或者Comment
- 子节点
- 可能是DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或者Comment
- document.documentElement:指向html
- document.body:指向body
- DocumentType:document.doctype处理<!DOCTYPE>。各浏览器处理方式不同
- 注释:各浏览器处理方式也不同
- 由于这些差异性存在,所以一般没什么用处。也不需要调用appendChild()等方法进行处理
- 文档信息(作为HTMLDocument实例)
- 网页的信息(放在<head>中)
- document.title:动态改变标题栏标题(文档中的title属性不变)
- document.url/document.domain/document.referrer:
- 不建议改变,没多大用处:但是将多个框架的document.domain改一致,有助于通信。
- domain不可以改的完全不同
- 不可将松散的(wrox.com)改作紧绷的(p2p.wrox.com)
- 查找元素
- getElementById():区分大小写,返回第一个找到的id(有时会返回同名的name,所以建议name和id属性不要一样)
- getElementsByTagName():返回类似NodeList的HTMLCollection对象。
- getElementsByTagName(*):顺序返回所有元素
- getElementsByTagName("*"):返回注释元素
- HTMLCollection对象:
- 方括号传入数字==调用item()方法
- 方括号传入字符==调用nameItem()方法通过name属性获取
- getElementsByName():HTMLDocument类型的方法
- TIPS:
- 1、对于表单元素,id不同以使<label>元素应用到单个标签
- 2、name相同以确保三个值只有一个给浏览器
- 特殊集合(HTMLDocument对象):document.anchors/document.applets(已不用)/document.forms/document.images/document.links
- DOM一致性检测:hasFeature()+必要的能力检测
- 文档写入:write()/writeln()/open()/close()
- 注意写入"<script>...</script>"的时候分开来,并写作"<script>..."+"<\/script>"
- 在文档调用完毕之后执行write()/writeln()方法会重写整个页面
- Element类型(元素节点),主要用于表现XML或者HTML文档
- 特征:nodeType为1,nodeName为标签名,nodeValue为null,parentNode/子节点灵活。
- nodeName属性、tagName属性访问元素的标签名,后者一律返回大写,注意这点,尤其在HTML文档中,可全部处理成小写。
- HTML元素(由HTMLElement或者其子类型表示)
- id/title/lang/dir/className属性。注意到没有用class是因为“class”是JS的保留字
- 格式 div.id
- 取得特性:getAttribute()/setAttribute()/removeAttribute()
- getAttribute()可以取得自定义特性(不区分大小写,且HTML5中自定义特性需要加上data-前缀),而这些自定义属性是不会调价到DOM元素的属性中的(IE除外)。
- 设置特性:setAttribute()、removeAttribute()
- 会处理自定义属性,特姓名统一转换为小写
- attributes属性(处理属性节点)
- Element类型是使用这个属性的唯一一个DOM节点类型
- 包含类似NodeList的NamedNodeMap集合。
- element.attributes.getNamedItem("id").nodeValue
element.attributes["id"].nodeValue
element.attributes.removeNamedItem("id")//返回删除的属性节点
element.attributes.setNamedItem("newAttr"); - 不常使用。可以用在遍历元素特性上。
- 返回的特性的顺序不一定。
- 创建元素
- document.creatElement("div")
- IE中 :document.creatElement("html代码")//避开IE的一些漏洞
- 元素子节点:对于某个标签下有多少个子节点的问题
- IE只考虑元素类型
- 其他浏览器还会考虑换行符(文本类型)
- 所以注意考虑nodeType
- 当然可以使用getElementById()或者getElementsByTagName()
- 访问or设置元素的属性方法
- HTMLElement或者子类型方法
- getAttribute()/setAttribute()/removeAttribute()
- DOM对象的属性来访问:不会设置自定义属性
- attributes属性
- 注意:style和onclick等在getAttribute()和DOM属性调用中返回的不一样:style通过getAttribute()返回CSS文本,属性返回对象;onclick等通过属性返回JS函数,getAttribute返回代码的字符串。
- 若以编程方式操作的时候,避免使用getAttribute();getAttribute()适合处理自定义属性。
- Text类型
- 通过nodeValue属性、data属性以及createTextNode()等方式都可以访问或者修改Text节点(但是注意,修改时传入的字符串会经过HTML或XML的编码)
- createTextNode()创建文本节点
- 在element上调用element.normalize()规范下面的文本节点
- splitText(index)分割文本节点,分割到index前一个位置为止。
- 注意到:可以通过lastChild访问元素的文本节点(文本节点通常是元素的最后一个节点?)
- Comment类型(注释)
- 不支持子节点,但可以通过父节点访问(comment.data)。
- 与Text类型继承自相同基类,拥有除了splitText()外的方法
- document.createComment()
- 少用。需要确保在<html></html>间,不然会有不同的处理方式(参照Document节点类型那里的讨论)
- CDATASection类型(针对基于XML的文档,表示CDATA区域)
- 同样继承自Text类型,拥有除了splitText()外的方法
- document.createCDataSection()
- DocumentType类型
- 仅FireFox、Safari、Opera和Chrome4.0支持
- DOM1中不能动态创建,只能通过document.doctype访问
- 属性:name、entities、notations;针对HTML和XHTML,后两者都是空列表。所以name属性比较有用,保存文档类型的名称。
- 之前也提到过,不同浏览器的处理方式不同。
- DocumentFragment类型
- 轻量级。只有它在文档中没有对应的标记。包含和控制节点,但不占用额外资源。
- 可以做“仓库”使用,保存将来可能会添加到文档中的节点(待通过appendChild()等方法添加至树中,或者从树中移除的)
- 创建:document.createDocumentFragment();
- 文档片段,继承了Node的所有方法。
- 例如,为ul添加3个li:
var fragment = document.createDocumentFragment();
var ul = document.getElementById("ul");
var li = null;
for (var i=0;i<3;i++){
li = document.createElement("li");
li.appendChild(document.createTextNode("Item"+(i+1));
fragment.appendChild(li);
}
ul.appendChild(fragment); //fragment中的所有li被删除并转移到ul中
- Attr类型(属性类型)
- 一般不认为是节点树中的成员,常使用其它方法访问,而不是使用属性节点进行操作。
- 属性:name/value/specified
- document.createAttribute()
- element.setAttributeNode(attr);//其中attr是属性节点
element.getAttributeNode(attr)
DOM操作技术
-
- 动态脚本
- 通过DOM动态添加JS脚本<script>
- 添加外部脚本:何时加载完毕无法确认,但可以通过“事件”进行控制。
- 添加内嵌脚本:普遍支持向script标记添加文本节点,但IE不允许访问script的子节点,则可通过对script.text进行处理来添加这个内嵌脚本。
- 动态样式
- 通过DOM动态应用CSS样式:
- 动态脚本
- link添加样式表;
- style添加嵌入样式;
- 修改元素的属性:
-
- 修改元素的样式属性;
- 修改元素的class属性以匹配已存在的样式
- 外部样式表是异步加载的,是否知道样式表已经加载完成并不重要。但同样可以通过“事件”对这个过程进行检测。
- 对于<style>标记,IE同样不可以访问其子节点,这个时候可以通过style.stylesheet.cssText处理。注意处理这个属性有可能导致浏览器崩溃。
- <table>,<tbody>/<thead>,<tr>,<th>/<td>对于多行多列的表格需要进行大量的重复性工作,HTML DOM为此给<table>,<tbody>,<tr>增加了一些属性和方法。
- 还可以利用document.write() or innerHTML写入创建的代码,其中行列可以通过双重for循环处理,注意需要采取尽量少的调用innerHTML的循环算法,考虑内存的使用情况。
- 以及NamedNodeMap和HTMLCollection都是类数组对象,且都是动态的:文档结构变化的时候,他们随之改变(实时运行的查询)
- 每次重新访问NodeList等的时候,都会重新对NodeList进行查询,更新其length属性,所以效率略低,应该减少对NodeList等的访问,并且可以将其中取得的值缓存起来使用(而不是每次都去查询一遍)
- 理解DOM的关键,就是理解DOM对性能的影响。DOM操作往往是JS程序最大的开销部分。