本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-07-03
DOM1
级主要定义的是HTML
和XML
文档的底层结构。DOM2
和DOM3
级则在这个结构的基础上引入了更多的交互能力,也支持了更高级的XML
特性。
DOM2
级核心(DOM Level 2 Core
):在1 级核心基础上构建,为节点添加了更多方法和属性。DOM2
级视图(DOM Level 2 Views
):为文档定义了基于样式信息的不同视图。DOM2
级事件(DOM Level 2 Events
):说明了如何使用事件与DOM
文档交互。DOM2
级样式(DOM Level 2 Style
):定义了如何以编程方式来访问和改变CSS
样式信息。DOM2
级遍历和范围(DOM Level 2 Traversal and Range
):引入了遍历DOM
文档和选择其特定部分的新接口。DOM2
级 HTML(DOM Level 2 HTML
):在1 级HTML
基础上构建,添加了更多属性、方法和新接口。有了XML
命名空间,不同XML
文档的元素就可以混合在一起,共同构成格式良好的文档,而不必担心发生命名冲突。从技术上说,HTML
不支持XML
命名空间,但XHTML
支持XML
命名空间。因此,本节给出的都是XHTML
的示例。
命名空间要使用xmlns
特性来指定。XHTML
的命名空间是http://www.w3.org/1999/xhtml
,在任格式良好XHTML
页面中,都应该将其包含在<html>
元素中;要想明确地为XML
命名空间创建前缀,可以使用xmlns
后跟冒号,之后为前缀;有时候为了避免不同语言间的冲突也可以使用命名空间来限定特性,比如下例中的class
特性
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
<xhtml:head>
<xhtml:title>Example XHTML page</xhtml:title>
</xhtml:head>
<xhtml:body xhtml:class="home">
Hello world!
</xhtml:body>
</xhtml:html>
在只基于一种语言编写XML
文档的情况下,命名空间实际上也没有什么用。不过,在混合使用两种语言的情况下,命名空间的用处就非常大了。混合了XHTML
和SVG
语言的文档:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example XHTML page</title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 100 100" style="width:100%; height:100%">
<rect x="0" y="0" width="100" height="100" style="fill:red"/>
</svg>
</body>
</html>
在这个例子中,通过设置命名空间,将<svg>
标识为了与包含文档无关的元素。此时,<svg>
元素的所有子元素,以及这些元素的所有特性,都被认为属于http://www.w3.org/2000/svg
命名空间。即使这个文档从技术上说是一个XHTML
文档,但因为有了命名空间,其中的SVG
代码也仍然是有效的。
1、 Node 类型的变化
在DOM2
级中,Node
类型包含下列特定于命名空间的属性。
localName
:不带命名空间前缀的节点名称。namespaceURI
:命名空间URI或null
。prefix
:命名空间前缀或null
。下例中,对于<html>
元素来说,它的localName
和tagName
是"html"
,namespaceURI
是"http://www.w3.org/1999/xhtml"
,而prefix
是null
。对于<s:svg>
元素而言,它的localName
是"svg"
,tagName
是"s:svg"
,namespaceURI
是"http://www.w3.org/2000/svg"
,而prefix
是"s"
。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example XHTML page</title>
</head>
<body>
<s:svg xmlns:s="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 100 100" style="width:100%; height:100%">
<s:rect x="0" y="0" width="100" height="100" style="fill:red"/>
</s:svg>
</body>
</html>
DOM3
级在此基础上更进一步,又引入了下列与命名空间有关的方法。
isDefaultNamespace(namespaceURI)
:判断指定的namespaceURI
是否是当前节点的默认命名空间,返回布尔值lookupNamespaceURI(prefix)
:返回给定prefix
的命名空间(namespaceURI
)。lookupPrefix(namespaceURI)
:返回给定namespaceURI
的前缀(prefix
)。2、Document 类型的变化
createElementNS(namespaceURI, tagName)
:使用给定的tagName
创建一个属于命名空间namespaceURI
的新元素。createAttributeNS(namespaceURI, attributeName)
:使用给定的attributeName
创建一个属于命名空间namespaceURI
的新特性。getElementsByTagNameNS(namespaceURI, tagName)
:返回属于命名空间namespaceURI
的tagName
元素的NodeList
。3、Element 类型的变化
getAttributeNS(namespaceURI,localName)
:取得属于命名空间namespaceURI
且名为localName
的特性。getAttributeNodeNS(namespaceURI,localName)
:取得属于命名空间namespaceURI
且名为localName
的特性节点。getElementsByTagNameNS(namespaceURI, tagName)
:返回属于命名空间namespaceURI
的tagName
元素的NodeList
。hasAttributeNS(namespaceURI,localName)
:确定当前元素是否有一个名为localName
的特性,而且该特性的命名空间是namespaceURI
。注意,“DOM2 级核心”也增加了一个hasAttribute()
方法,用于不考虑命名空间的情况。removeAttriubteNS(namespaceURI,localName)
:删除属于命名空间namespaceURI
且名为localName
的特性。setAttributeNS(namespaceURI,qualifiedName,value)
:设置属于命名空间namespace-URI
且名为qualifiedName
的特性的值为value
。setAttributeNodeNS(attNode)
:设置属于命名空间namespaceURI
的特性节点。1、DocumentType
类型的变化
DocumentType
类型新增了3 个属性:publicId
、systemId
和internalSubset
(内部子集), 前两个属性表示的是文档类型声明中的两个信息段,这两个信息段在DOM1
级中是没有办法访问到的。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
[<!ELEMENT name (#PCDATA)>] >
publicId
:"-//W3C//DTD HTML 4.01//EN"
systemId
:"http://www.w3.org/TR/html4/strict.dtd"
document.doctype.internalSubset
:"<!ELEMENT name (#PCDATA)>"
。
2、Document
类型的变化
2.1、 importNode
这个方法的用途是从一个文档中取得一个节点,然后将其导入到另一个文档,使其成为这个文档结构的一部分。需要注意的是,每个节点都有一个ownerDocument
属性,表示所属的文档。
如果调用appendChild
时传入的节点属于不同的文档,ownerDocument
属性的值不一样,则会导致错误。但在调用importNode
时传入不同文档的节点则会返回一个新节点,这个新节点的所有权归当前文档所有。
importNode
方法与cloneNode
方法非常相似,它接受两个参数(要复制的节点、是否复制子节点的布尔值),返回的结果是原来节点的副本,但能够在当前文档中使用。
var newNode = document.importNode(oldNode, true); //导入节点及其所有子节点
document.body.appendChild(newNode);
// 这个方法在HTML文档中并不常用,在XML文档中用得比较多
2.2、defaultView
属性
保存着一个指针,指向拥有给定文档的窗口(或框架),除 IE
之外的所有浏览器都支持defaultView
属性。在IE
中有一个等价的属性名叫parentWindow
(Opera 也支持这个属性)。因此,要确定文档的归属窗口,可以使用以下代码。
var parentWindow = document.defaultView || document.parentWindow;
2.3、“DOM2 级核心”还为document.implementation
对象规定了两个新方法。
createDocumentType
:用于创建一个新的DocumentType
节点,接受3 个参数:文档类型名称、publicId
、systemId
createDocument
:用于创建新文档,这个方法接受3 个参数:针对文档中元素的namespaceURI
、文档元素的标签名、新文档的文档类型。要想创建一个XHTML
文档,可以使用以下代码。
var doctype = document.implementation.createDocumentType(
"html",
" -//W3C//DTD XHTML 1.0 Strict//EN",
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
);
var doc = document.implementation.createDocument("http://www.w3.org/1999/xhtml","html", doctype);
// 不过,新文档当前只有文档元素`<html>`,剩下的所有元素都需要继续添加。
2.4、“DOM2 级HTML”模块也为document.implementation
新增了一个方法,名叫createHTMLDocument
。这个方法的用途是创建一个完整的HTML
文档,是HTMLDocument
类型的实例,因而具有该类型的所有属性和方法,包括title
和body
属性
var htmldoc = document.implementation.createHTMLDocument("New Doc");
alert(htmldoc.title); //"New Doc"
alert(typeof htmldoc.body); //"object"
3、Node 类型的变化
DOM3
级引入了两个辅助比较节点的方法:isSameNode()
和isEqualNode()
。这两个方法都接受一个节点参数,并在传入节点与引用的节点相同或相等时返回true
。所谓相同isSameNode()
,指的是两个节点引用的是同一个对象。所谓相等isEqualNode()
,指的是两个节点是相同的类型,具有相等的属性(nodeName
、nodeValue
,等等),而且它们的attributes
和childNodes
属性也相等(相同位置包含相同的值)。
var div1 = document.createElement("div");
div1.setAttribute("class", "box");
var div2 = document.createElement("div");
div2.setAttribute("class", "box");
alert(div1.isSameNode(div1)); //true
alert(div1.isEqualNode(div2)); //true
alert(div1.isSameNode(div2)); //false
4、框架的变化
框架和内嵌框架分别用HTMLFrameElement
和HTMLIFrameElement
表示,它们在DOM2
级中都有了一个新属性,名叫contentDocument
。这个属性包含一个指针,指向表示框架内容的文档对象。在此之前,无法直接通过元素取得这个文档对象(只能使用frames
集合)。IE8 之前不支持框架中的contentDocument
属性,但支持一个名叫contentWindow
的属性,该属性返回框架的window
对象,而这个window
对象又有一个document
属性
var iframe = document.getElementById("myIframe");
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
访问框架或内嵌框架的文档对象要受到跨域安全策略的限制。如果某个框架中的页面来自其他域或不同子域,或者使用了不同的协议,那么要访问这个框架的文档对象就会导致错误。
任何支持style
特性的HTML
元素在JavaScript
中都有一个对应的style
属性。这个style
对象是CSSStyleDeclaration
的实例,包含着通过HTML
的style
特性(属性)指定的所有样式信息,但不包含与外部样式表或嵌入样式表经层叠而来的样式。
在style
特性中指定的任何CSS
属性都将表现为这个style
对象的相应属性。对于使用短划线分隔的词汇,例如background-image
的CSS
属性名,必须将其转换成驼峰大小写形式,才能通过JavaScript
来访问。
多数情况下,都可以通过简单地转换属性名的格式来实现转换。也有不能直接转换的CSS
属性就是float
。由于float
是JavaScript
中的保留字,因此不能用作属性名。DOM2级样式
规范规定样式对象上相应的属性名应该是cssFloat
,而IE支持的则是styleFloat
。还有就是class
也是保留字,所以需要使用className
来访问。
在以这种方式改变样式时,元素的外观会自动被更新。使用JavaScript
操作style
对象,都是操作的行内样式,层级较高,不会被覆盖。也可以通过style
对象取到相应的特性的值,如果没有为元素设置style
特性,那么style
对象中可能会包含一些默认的值,但这些值并不能准确地反映该元素的样式信息。
1、DOM 样式属性和方法
cssText
:通过它能够访问到style
特性(行内属性)中的CSS
代码。length
:应用给元素的CSS
属性的数量。parentRule
:表示CSS
信息的CSSRule
对象。本节后面将讨论CSSRule
类型。getPropertyPriority(propertyName)
:如果给定的属性使用了!important
设置,则返回"important"
;否则,返回空字符串。getPropertyValue(propertyName)
:返回给定属性的字符串值。item(index)
:返回给定位置的CSS
属性的名称。removeProperty(propertyName)
:从样式中删除给定属性。setProperty(propertyName,value,priority)
:将给定属性设置为相应的值,并加上优先权标志("important"
或者一个空字符串)。2、计算样式
2.1、getComputedStyle()
方法
这个方法接受两个参数:要取得计算样式的元素和一个伪元素字符串(例如":after"
)。如果不需要伪元素信息,第二个参数可以是null
。getComputedStyle()
方法返回一个CSSStyleDeclaration
对象(与style
属性的类型相同),其中包含当前元素的所有计算的样式(最终样式)
<!DOCTYPE html>
<html>
<head>
<title>Computed Styles Example</title>
<style type="text/css">
#myDiv {
background-color: blue;
width: 100px;
height: 200px;
}
</style>
</head>
<body>
<div id="myDiv" style="background-color: red; border: 1px solid black"></div>
<script>
var myDiv = document.getElementById("myDiv");
var computedStyle = document.defaultView.getComputedStyle(myDiv, null);
alert(computedStyle.backgroundColor); // "red"/ "rgb(255,0,0)" 嵌入样式会被行内样式覆盖掉
alert(computedStyle.width); // "100px"
alert(computedStyle.height); // "200px"
alert(computedStyle.border); // 在某些浏览器中是"1px solid black" / "1px solid rgb(0,0,0)"
</script>
</body>
</html>
在设置border 时, 实际上是设置了四个边的边框宽度、颜色、样式属性(border-left-width
、border-top-color
、border-bottom-style
)。因此, 即使computedStyle.border
不会在所有浏览器中都返回值,但computedStyle.borderLeftWidth
会返回值。
所有计算的样式都是只读的,计算后的样式也包含属于浏览器内部样式表的样式信息,因此任何具有默认值的CSS
属性都会表现在计算后的样式中。
CSSStyleSheet
类型表示的是样式表,使用document.styleSheets
可以拿到所有包括通过<link>
元素包含的样式表和在<style>
元素中定义的样式表。
CSSStyleSheet
继承自StyleSheet
,从StyleSheet
接口继承而来的属性如下。
disabled
:表示样式表是否被禁用的布尔值。将这个值设置为true
可以禁用样式表。href
:如果样式表是通过<link>
包含的,则是样式表的URL
;否则,是null
。<style>
元素包含的样式表没有href
属性media
:当前样式表支持的所有媒体类型的集合。也有一个length
属性和一个item()
方法。也可以使用方括号语法取得集合中特定的项。如果集合是空列表,表示样式表适用于所有媒体。在IE 中,media
是一个反映<link>
和<style>
元素media
特性值的字符串。ownerNode
:指向拥有当前样式表的节点的指针,样式表可能是在HTML
中通过<link>
或<style/>
引入的(在XML
中可能是通过处理指令引入的)。如果当前样式表是其他样式表通过@import
导入的,则这个属性值为null
。IE 不支持这个属性。parentStyleSheet
:在当前样式表是通过@import
导入的情况下,这个属性是一个指向导入它的样式表的指针。title
:ownerNode
中title
属性的值。type
:表示样式表类型的字符串。对CSS
样式表而言,这个字符串是"type/css"
。除了disabled
属性之外,其他属性都是只读的
CSSStyleSheet
类型自身的属性和方法如下,多与Rule
有关
cssRules
:样式表中包含的样式规则的集合。IE
不支持这个属性,但有一个类似的rules
属性。ownerRule
:如果样式表是通过@import
导入的,这个属性就是一个指针,指向表示导入的规则;否则,值为null
。IE
不支持这个属性。deleteRule(index)
:删除cssRules
集合中指定位置的规则。IE
不支持这个方法,但支持一个类似的removeRule()
方法。insertRule(rule,index)
:向cssRules
集合中指定的位置插入rule
字符串。IE
不支持这个方法,但支持一个类似的addRule()
方法。不同浏览器的document.styleSheets
返回的样式表也不同。所有浏览器都会包含<style>
元素和rel
特性被设置为"stylesheet"
的<link>
元素引入的样式表。IE
和Opera
也包含rel
特性被设置为"alternate stylesheet"
的<link>
元素引入的样式表。
也可以直接通过<link>
或<style>
元素取得CSSStyleSheet
对象。DOM 规定了<link>
和<style>
元素包含了一个CSSStyleSheet
对象的属性,名叫sheet
;除了IE,其他浏览器都支持这个属性。IE 支持的是styleSheet
属性。
function getStyleSheet(element){
return element.sheet || element.styleSheet;
}
//取得第一个<link/>元素引入的样式表
var link = document.getElementsByTagName("link")[0];
var sheet = getStylesheet(link);
CSSRule
对象表示样式表中的每一条规则,例如 body { background-color:"red"}
,CSSRule
是一个供其他多种类型继承的基类型,其中最常见的就是CSSStyleRule
类型,表示样式信息(其他规则还有@import
、@font-face
、@page
和@charset
,但这些规则很少有必要通过脚本来访问)
CSSStyleRule
对象包含下列属性。
cssText
:返回整条规则对应的文本,各浏览器的返回也不尽相同,IE 不支持这个属性。parentRule
:如果当前规则是导入的规则,这个属性引用的就是导入规则;否则,这个值为null
。IE 不支持这个属性。parentStyleSheet
:当前规则所属的样式表。IE 不支持这个属性。selectorText
:返回当前规则的选择符文本。例如"#myDiv","h1",".color"
,在Firefox、Safari、Chrome 和IE 中这个属性是只读的。Opera 允许修改selectorText
。style
:一个CSSStyleDeclaration
对象,可以通过它设置和取得规则中特定的样式值。type
:表示规则类型的常量值。对于样式规则,这个值是1。IE 不支持这个属性。假设这条规则位于页面中的第一个样式表中。
div.box {
background-color: blue;
width: 100px;
height: 200px;
}
var sheet = document.styleSheets[0]; // 取得样式表中的第一个
var rules = sheet.cssRules || sheet.rules; //取得规则列表
var rule = rules[0]; //取得第一条规则
alert(rule.selectorText); // div.box
alert(rule.style.cssText); //完整的CSS 代码
alert(rule.style.backgroundColor); //'blue'
alert(rule.style.width); //'100px'
alert(rule.style.height); //'200px'
rule.style.backgroundColor = "red"
// 也可以通过rule 的 style设置样式 但是会应用到所有带有.box类的div上
insertRule()
和IE的addRule()
insertRule()
接受两个参数:规则文本和表示在哪里插入规则的索引。
addRule()
也接收两必选参数:选择符文本和CSS
样式信息;一个可选参数:插入规则的位置。
var sheet = document.styleSheets[0]; // 取得样式表中的第一个
sheet.insertRule("body { background-color: silver }", 0); //DOM 方法
sheet.addRule("body", "background-color: silver", 0); //仅对IE 有效
deleteRule()
和IE的removeRule()
这两个方法接受一个参数:要删除的规则的位置。例如,要删除样式表中的第一条规则
sheet.deleteRule(0); //DOM 方法
sheet.removeRule(0); //仅对IE 有效
1、偏移量
offsetHeight
:元素在垂直方向上占用的空间大小,包括元素的高度、水平滚动条的高度、边框高度。offsetWidth
:元素在水平方向上占用的空间大小,包括元素的宽度、垂直滚动条的宽度、边框宽度。offsetLeft
:元素的左外边框至包含元素的左内边框之间的像素距离。offsetTop
:元素的上外边框至包含元素的上内边框之间的像素距离。要想知道某个元素在页面上的偏移量,可以通过循环至根元素累加它们的offsetLeft
/ offsetTop
function getElementLeft(element){
var actualLeft = element.offsetLeft;
var current = element.offsetParent;
while (current !== null){
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
return actualLeft;
}
所有这些偏移量属性都是只读的,而且每次访问它们都需要重新计算。因此,应该尽量避免重复访问这些属性;如果需要重复使用其中某些属性的值,可以将它们保存在局部变量中,以提高性能。
2、客户区大小
元素的客户区大小,指的是元素内容及其内边距所占据的空间大小。
clientWidth
是元素内容区宽度加上左右内边距宽度;clientHeight
属性是元素内容区高度加上上下内边距高度。// 判读是否是混杂模式来兼容IE,也是只读的,也是每次访问都要重新计算的。
function getViewport(){
if (document.compatMode == "BackCompat"){
return {
width: document.body.clientWidth,
height: document.body.clientHeight
};
} else {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
};
}
}
3、滚动大小
滚动大小指的是包含滚动内容的元素的大小。有些元素(例如<html>
元素),即使没有执行任何代码也能自动地添加滚动条;但另外一些元素,则需要通过CSS
的overflow
属性进行设置才能滚动。
scrollHeight
:在没有滚动条的情况下,元素内容的总高度。scrollWidth
:在没有滚动条的情况下,元素内容的总宽度。scrollLeft
:被隐藏在内容区域左侧的像素数。通过设置这个属性可以改变元素的滚动位置。scrollTop
:被隐藏在内容区域上方的像素数。通过设置这个属性可以改变元素的滚动位置。scrollWidth
和scrollHeight
主要用于确定元素内容的实际大小。
4、确定元素大小
每个元素都提供了一个getBoundingClientRect()
方法。这个方法返回会一个矩形对象,包含4 个属性:left
、top
、right
和bottom
。这些属性给出了元素在页面中相对于视口的坐标位置。(IE8及更早的版本将左上角坐标认定为(2,2)
,其他则认定为(0,0)
)
对于不支持getBoundingClientRect()
的浏览器,可以通过其他手段取得相同的信息。一般来说,坐标轴中right
和left
差值与offsetWidth
的值相等,而坐标轴中bottom
和top
的差值与offsetHeight
相等,left
和top
可以根据offsetLeft
和offsetTop
拿到(注意使用循环父元素,因为拿到的是距离父元素的,而不是距离文档边界的),而right
和bottom
就可以使用left + offsetWidth
和top + offsetHeight
拿到了。
“DOM2 级遍历和范围”模块定义了两个用于辅助完成顺序遍历DOM
结构的类型:NodeIterator
和TreeWalker
。这两个类型能够基于给定的起点对DOM
结构执行深度优先(depth-first
)的遍历操作,IE 不支持DOM 遍历。
NodeIterator
类型是两者中比较简单的一个,可以使用document.createNodeIterator()
方法创建它的新实例。这个方法接受下列4 个参数。
root
:想要作为搜索起点的树中的节点。whatToShow
:表示要访问哪些节点的数字代码。filter
:是一个NodeFilter
对象,或者一个表示应该接受还是拒绝某种特定节点的函数。entityReferenceExpansion
:布尔值,表示是否要扩展实体引用。这个参数在HTML
页面中没有用,因为其中的实体引用不能扩展。whatToShow
参数是一个位掩码,通过应用一或多个过滤器(filter
)来确定要访问哪些节点。这个参数的值以常量形式在NodeFilter
类型中定义
NodeFilter.SHOW_ALL
:显示所有类型的节点。NodeFilter.SHOW_ELEMENT
:显示元素节点。NodeFilter.SHOW_ATTRIBUTE
:显示特性节点。由于DOM结构原因,实际上不能使用这个值。NodeFilter.SHOW_TEXT
:显示文本节点。NodeFilter.SHOW_CDATA_SECTION
:显示CDATA
节点。对HTML页面没有用。NodeFilter.SHOW_ENTITY_REFERENCE
:显示实体引用节点。对HTML 页面没有用。NodeFilter.SHOW_ENTITYE
:显示实体节点。对HTML 页面没有用。NodeFilter.SHOW_PROCESSING_INSTRUCTION
:显示处理指令节点。对HTML 页面没有用。NodeFilter.SHOW_COMMENT
:显示注释节点。NodeFilter.SHOW_DOCUMENT
:显示文档节点。NodeFilter.SHOW_DOCUMENT_TYPE
:显示文档类型节点。NodeFilter.SHOW_DOCUMENT_FRAGMENT
:显示文档片段节点。对HTML 页面没有用。NodeFilter.SHOW_NOTATION:显示符号节点
。对HTML 页面没有用。可以使用按位或操作符来组合多个选项
var whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
createNodeIterator()
方法的filter
参数来指定自定义的NodeFilter
对象(不使用上面说到的常量形式的),每个NodeFilter
对象只有一个方法,即acceptNode()
;如果应该访问给定的节点,该方法返回NodeFilter.FILTER_ACCEPT
,如果不应该访问给定的节点,该方法返回NodeFilter.FILTER_SKIP
。
由于NodeFilter
是一个抽象的类型,因此不能直接创建它的实例。在必要时,只要创建一个包含acceptNode()
方法的对象,然后将这个对象传入createNodeIterator()
中即可。
下列代码展示了如何创建一个只显示<p>
元素的节点迭代器。
var filter = {
acceptNode: function(node){
return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
};
var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);
上面的通过document.createNodeIterator()
方法得到的iterator
,就是NodeIterator
类型,这个类型拥有nextNode()
和previousNode()
这两个方法来遍历DOM子树,nextNode()
方法用于向前前进一步,而previousNode()
用于向后后退一步。在刚刚创建的NodeIterator
对象中,有一个内部指针指向根节点,因此第一次调用nextNode()
会返回根节点。向下遍历nextNode()
到最后一个节点时返回null
,向上遍历previousNode()
也是一样,遍历到第一个节点时会返回null
<div id="div1">
<p><b>Hello</b> world!</p>
<ul>
<li>List item 1</li>
<li>List item 2</li>
<li>List item 3</li>
</ul>
</div>
var div = document.getElementById("div1");
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false);
var node = iterator.nextNode(); // 拿到根元素div
while (node !== null) {
alert(node.tagName); //输出标签名
node = iterator.nextNode();
}
TreeWalker
是NodeIterator
的一个更高级的版本。除了包括nextNode()
和previousNode()
在内的相同的功能之外,这个类型还提供了下列用于在不同方向上遍历DOM
结构的方法。
parentNode()
:遍历到当前节点的父节点;firstChild()
:遍历到当前节点的第一个子节点;lastChild()
:遍历到当前节点的最后一个子节点;nextSibling()
:遍历到当前节点的下一个同辈节点;previousSibling()
:遍历到当前节点的上一个同辈节点。创建TreeWalker
对象要使用document.createTreeWalker()
方法,这个方法接受的4 个参数与document.createNodeIterator()
方法相同:作为遍历起点的根节点、要显示的节点类型、过滤器和一个表示是否扩展实体引用的布尔值。
在这里,filter
可以返回的值有所不同。除了NodeFilter.FILTER_ACCEPT
和NodeFilter.FILTER_SKIP
之外,还可以使用NodeFilter.FILTER_REJECT
。使用SKIP
会跳过当前节点,而使用REJECT
会跳过当前节点及该节点的子节点。如果使用REJECT
情况下,使用自定义filter
过滤的时候,不包含根节点的话,将不会访问任何一个节点,因为nextNode()
第一个访问的就是根节点,如果照之前定义的filter
过滤只要p元素的,三元表达式第一次就会拿到false
,取得REJECT
,从而跳过第一个元素(根元素DIV)及其子节点,所以将访问不到任何一个节点。
TreeWalker
真正强大的地方在于能够在DOM
结构中沿任何方向移动。使用TreeWalker
遍历DOM
树,即使不定义过滤器,也可以取得所有<li>
元素。
var div = document.getElementById("div1");
var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null, false);
walker.firstChild(); //转到<p>
walker.nextSibling(); //转到<ul>
var node = walker.firstChild(); //转到第一个<li>
while (node !== null) {
alert(node.tagName);
node = walker.nextSibling();
}
TreeWalker
类型还有一个属性,名叫currentNode
,表示任何遍历方法在上一次遍历中返回的节点。通过设置这个属性也可以修改遍历继续进行的起点,如下面的例子所示。
var node = walker.nextNode();
alert(node === walker.currentNode); //true
walker.currentNode = document.body; //修改起点
DOM2 级遍历和范围 模块定义了“范围”接口。通过范围可以选择文档中的一个区域,而不必考虑节点的界限(选择在后台完成,对用户是不可见的)。在常规的DOM 操作不能更有效地修改文档时,使用范围往往可以达到目的。
DOM2
级在Document
类型中定义了createRange()
方法。在兼容DOM 的浏览器中,这个方法属于document
对象。直接检测该方法可以确定浏览器是否支持范围。
var alsoSupportsRange = (typeof document.createRange == "function");
使用createRange()
来创建DOM范围。
var range = document.createRange();
每个范围由一个Range
类型的实例表示,这个实例拥有很多属性和方法:
startContainer
:包含范围起点的节点(即选区中第一个节点的父节点)。endContainer
:包含范围终点的节点(即选区中最后一个节点的父节点)。commonAncestorContainer
:startContainer
和endContainer
共同的祖先节点在文档树中位置最深的那个。startOffset
:范围在startContainer
中起点的偏移量。如果startContainer
是文本节点、注释节点或CDATA
节点,那么startOffset
就是范围起点之前跳过的字符数量。否则,startOffset
就是范围中第一个子节点的索引。endOffset
:范围在endContainer
中终点的偏移量(与startOffset
遵循相同的取值规则)。selectNode()
或selectNodeContents()
这两个方法都接受一个参数,即一个DOM 节点,然后使用该节点中的信息来填充范围,selectNode()
方法选择整个节点,包括其子节点;而selectNodeContents()
方法则只选择节点的子节点。
<body>
<p id="p1">
<b>Hello</b> world!
</p>
</body>
<script>
var range1 = document.createRange();
var range2 = document.createRange();
var p1 = document.getElementById("p1");
range1.selectNode(p1);
console.log(range1.startContainer); // body 传入节点的父节点
console.log(range1.endContainer); // body
console.log(range1.commonAncestorContainer); // body
// 打印出1 --> 给定节点在其父节点的childNodes中的索引(body与p之间的空格算作文本节点)
console.log(range1.startOffset);
// 打印出2 --> 等于startOffset + 范围中选择中的节点个数(只选择了一个p元素)
console.log(range1.endOffset);
range2.selectNodeContents(p1);
console.log(range2.startContainer); // p 等于传入的节点即p元素
console.log(range2.endContainer); // p
console.log(range2.commonAncestorContainer); // p
console.log(range2.startOffset); // 0 始终为0,因为范围是从给定节点的第一个子节点开始的
console.log(range2.endOffset); // 2 等于给点节点的子节点数量 node.children.length
</script>
setStartBefore(refNode)
将范围的起点设置在refNode
之前,因此refNode
也就是范围选区中的第一个子节点。同时会将startContainer
属性设置为refNode.parentNode
,将startOffset
属性设置为refNode
在其父节点的childNodes
集合中的索引。
setStartAfter(refNode)
将范围的起点设置在refNode
之后,因此refNode
也就不在范围之内了,其下一个同辈节点才是范围选区中的第一个子节点。同时会将startContainer
属性设置为refNode.parentNode
,将startOffset
属性设置为refNode
在其父节点的childNodes
集合中的索引加1。
setEndBefore(refNode)
将范围的终点设置在refNode
之前,因此refNode
也就不在范围之内了,其上一个同辈节点才是范围选区中的最后一个子节点。同时会将endContainer
属性设置为refNode.parentNode
,将endOffset
属性设置为refNode
在其父节点的childNodes
集合中的索引。(即最后一个子节点索引加1)
setEndAfter(refNode)
将范围的终点设置在refNode
之后,因此refNode
也就是范围选区中的最后一个子节点。同时会将endContainer
属性设置为refNode.parentNode
,将endOffset
属性设置为refNode
在其父节点的childNodes
集合中的索引加1。
setStart()
和setEnd()
方法这两个方法都接受两个参数:一个参照节点和一个偏移量值,对setStart()
来说,参照节点会变成startContainer
,而偏移量值会变成startOffset
。对于setEnd()
来说,参照节点会变成endContainer
,而偏移量值会变成endOffset
。
假设你只想选择前面HTML
示例代码中从"Hello"
的"llo"
到"world!"
的"o"
即llo</b> wo
<p id="p1"><b>Hello</b> world!</p>
<script>
var p1 = document.getElementById("p1");
var helloNode = p1.firstChild.firstChild; // p第一个子元素b的第一个子节点(即文本节点hello)
var worldNode = p1.lastChild; // p的第二个子节点(即文本节点 world!)
var range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);
</script>
即选择了llo</b> wo
,但是,范围知道自身缺少哪些开标签和闭标签,它能够重新构建有效的DOM
结构以便我们对其进行操作。DOM就会变成
<p><b>He</b><b>llo</b> world!</p>
,在此例中,会为范围内的llo</b>
添加开标签,也会为范围外的<b>He
添加闭合标签。
deleteContents()
这个方法能够从文档中删除范围所包含的内容,上面的例子中如果在最后调用range.deleteContents();
,DOM就会变成
<p><b>He</b>rld!</p>
由于范围选区在修改底层DOM
结构时能够保证格式良好,因此即使内容被删除了,最终的DOM
结构依旧是格式良好的。在此例中就是会为范围外的<b>He
添加闭合标签。
extractContents()
这个方法也会从文档中移除范围选区。但区别在于extractContents()
会返回范围的文档片段。
var fragment = range.extractContents();
p1.parentNode.appendChild(fragment);
最终DOM就会变成
<p><b>He</b>rld!</p>
<b>llo</b> wo
cloneContents()
创建范围对象的一个副本,然后在文档的其他地方插入该副本。不会移除范围选区
var fragment = range.cloneContents();
p1.parentNode.appendChild(fragment);
最终DOM就会变成
<p><b>Hello</b> world!</p>
<b>llo</b> wo
// 跟使用删除的方法不同, p元素的选中范围是没有被移除的
insertNode()
可以向范围选区的开始处插入一个节点。
var span = document.createElement("span");
span.style.color = "red";
span.appendChild(document.createTextNode("Inserted text"));
range.insertNode(span);
最终DOM会变成
<p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>
surroundContents()
这个方法接受一个参数,即环绕范围内容的节点。在环绕范围插入内容时,后台会执行下列步骤。
可以使用这种技术来突出显示网页中的某些词句
<p id="p1"><b>Hello</b> world!</p>
<script>
var p1 = document.getElementById("p1");
var helloNode = p1.firstChild.firstChild;
range = document.createRange();
range.selectNode(helloNode);
var span = document.createElement("span");
span.style.backgroundColor = "yellow";
range.surroundContents(span)
</script>
最终DOM会变成,"Hello"
这个单词会变成黄色背景,被标签span
包裹。
<p><b><span style="background-color:yellow">Hello</span></b> world!</p>
<!-- 为了插入<span>,范围必须包含整个DOM 选区(不能仅仅包含选中的DOM 节点)。 -->
collapse()
折叠DOM使用collapse()
方法来折叠范围(可以理解为清除范围,即范围起点终点重合,范围中什么都没有),这个方法接受一个参数,一个布尔值,表示要折叠到范围的哪一端。参数true
表示折叠到范围的起点,参数false
表示折叠到范围的终点。要确定范围已经折叠完毕,可以检查collapsed
属性
range.collapse(true); //折叠到起点
alert(range.collapsed); //输出true
检测某个范围是否处于折叠状态,可以帮我们确定范围中的两个节点是否紧密相邻。即这个范围是否被清除,是否什么都没有
<p id="p1">Paragraph 1</p><p id="p2">Paragraph 2</p>
<script>
var p1 = document.getElementById("p1");
var p2 = document.getElementById("p2");
var range = document.createRange();
range.setStartAfter(p1); // 将起点设置在p1后面
range.setStartBefore(p2); // 将终点设置在p2前面
alert(range.collapsed); //输出true --> p1的后面和p2的前面什么也没有。
</script>
compareBoundaryPoints()
比较范围这个方法接受两个参数:表示比较方式的常量值和要比较的范围。比较方式的常量值如下,
例:A.compareBoundaryPoints(Range.xxxx,B)
Range.START_TO_START
:比较A和B的起点;(A头 与 B头 )Range.START_TO_END
:比较A的起点和B的终点;(A头 与 B尾)Range.END_TO_END
:比较A和B的终点;(A尾 与 B尾)Range.END_TO_START
:比较B的终点和A的起点。(A尾 与 B头)compareBoundaryPoints()
方法可能的返回值如下:
如果第一个范围中的点位于第二个范围中的点之前,返回-1;如果两个点相等,返回0;如果第一个范围中的点位于第二个范围中的点之后,返回1。
cloneRange()
复制范围这个方法会创建调用它的范围的一个副本。新创建的范围与原来的范围包含相同的属性,而修改它的端点不会影响原来的范围。
var newRange = range.cloneRange();
detach()
清理范围在使用完范围之后,最好是调用detach()
方法,以便从创建范围的文档中分离出该范围。调用detach()
之后,就可以放心地解除对范围的引用,从而让垃圾回收机制回收其内存了
// 不再是清除范围,而是在文档中直接清理出去。
range.detach(); //从文档中分离
range = null; //解除引用