DOM(文档对象模型)是针对HTML和XML文档的一个API。DOM描述了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。
注意:IE中所有的DOM对象都是以COM对象的形式实现的。这意味着IE中的DOM对象和原生JavaScript对象的行为或活动特点并不一致。
文档节点是每个文档的根节点,文档节点通常只有一个子节点——文档元素(元素)。
文档元素是稳当的最外层元素,文档中的其他元素都包含在文档元素中。每个文档只能有一个文档元素。在HTML页面中,文档元素始终是元素,在XML中,没有预定义的元素,因此任何一个元素都可以成为文档元素。
DOM1级定义了一个Node接口,该接口将由DOM中所有节点类型实现。这个Node接口在JavaScript中是作为Node类型实现的;除了IE之外,在其他所有浏览器中都可以访问到这个类型。JavaScript中所有节点类型都继承自Node类型,因此所有节点类型都共享着基本属性和方法。每个节点都有一个nodeType
属性,用于表明节点的类型。节点类型由在Node类型中定义的下列12个数值常量来表示,任何节点类型必居其一:
1 | ELEMENT_NODE |
2 | ATTRIBUTE_NODE |
3 | TEXT_NODE |
4 | CDATA_SECTION_NODE |
5 | ENTITY_REFERENCE_NODE |
6 | ENTITY_NODE |
7 | PROCESSING_INSTRUCTION_NODE |
8 | COMMENT_NODE |
9 | DOCUMENT_NODE |
10 | DOCUMENT_TYPE_NODE |
11 | DOCUMENT_FRAGMENT_NODE |
12 | NOTATION_NODE |
确定节点类型时,可以将someNode.nodeType
与Node.XXX_NODE
常量相比较,然而,由于IE并没有公开Node类型的构造函数,因此在IE中不能这样确定节点类型。为了确保跨浏览器兼容,最好还是将someNode.nodeType
属性与数字值相比较,如下所示:
if (someNode.nodeType == 1) {
//适用于所有浏览器 1~12
}
并不是所有节点都受到Web浏览器的支持,开发者最常用的是元素节点和文本节点。
nodeName
和nodeValue
属性
要了解节点的具体信息,可以使用节点的nodeName
和nodeValue
属性。在使用这两个节点之前,最好先检测一下节点类型:
if (someNode.nodeType == 1) {
value = someNode.nodeName; //nodeName的值是元素的标签名
}
对于元素节点,nodeName
中保存的始终是元素的标签名,nodeValue
始终为null。
节点关系
每个节点都有一个childNodes
属性,其中保存着一个NodeList对象。NodeList是一种类数组对象,和arguments
一样,并非Array的实例。NodeList的独特之处在于,它实际上是基于DOM结构动态执行查询的结果。因此DOM结构的变化能够自动反映到NodeList对象中。我们常说,NodeList对象是有呼吸,有生命的对象,而不是我们在第一次访问它时拍下的一张快照。访问NodeList对象里的节点有两种方法,方括号(类似于数组)的方式和item()方法。示例:
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var cpunt = someNode.childNodes.length;//length属性表示childNodes对象中子节点的数量
在前面介绍过,使用Array.prototype.slice()
方法可以将arguments
对象转换为一个数组,同样,使用这个方法可以把childNodes
对象转换为数组:
//在IE8之前的版本中无效
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);
因为在IE8之前,childNodes
在IE中并非一个JScript对象,而是一个COM对象,使用上面的语法会抛出错误。在IE浏览器(IE8之前),需要手动枚举childNodes
中的每个子节点,最终组成一个数组:
function convertToArray(nodes) {
var array = null;
try {
array = Array.prototype.slice.call(nodes.childNodes, 0); //针对非IE浏览器
} catch (ex) {
array = new Array();
for (var i = 0;i < nodes.childNodes.length;i++) {
array.push(nodes[i]);
}
i = null;
}
return array;
}
每个节点都有一个parent
属性,里面是它的父节点。包含在childNodes
对象中的所有子节点都有一个共同的父节点。因此他们的parent
属性都指向同一个父节点。此外,包含在childNodes
列表中的节点都是同胞节点,通过每个节点的previousSibling
属性和nextSibling
属性可以访问到同一列表的其他节点。列表中第一个节点的previousSibling
属性为null,最后一个节点的nextSibling
属性为null。如下所示:
if (someNode.nextSibling === null) {
alert("Last node in the parent's childNodes list.")
} else if (someNode.previousSibling === null) {
alert("First node in the parent's childNodes list.")
}
如果childNodes
中只有一个节点,那它的previousSibling
属性和nextSibling
属性都为null。
父节点和第一个子节点、最后一个子节点也有特殊的关系。父节点的firstChild
和lastChild
分别指向childNotes
的第一个节点和最后一个节点。如果没有子节点,那么firstChild
和lastChild
都为null。仔细查看原书中图10-2。
hasChildNodes()
方法在父节点包含一个或者多个子节点时返回true
,没有子节点时返回false
。
最后是所有的节点都有的一个属性叫做ownerDocument
,该属性指向表示整个文档的文档节点。这种关系表示的是任何节点都属于它所在的文档。任何节点都不能同时存在于两个或更多文档中,通过这个属性,我们不必层层回溯到达顶端,而是可以直接访问文档节点。
虽然所有节点都继承自Node节点,但并非所有节点都有子节点。
操作节点
因为关系指针都是只读的,所以DOM提供了一系列操作节点的方法。
appendChild()
:添加一个新节点到父节点childNotes
的最后一个位置并返回这个节点
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(someNode.lastChild == newNode); //true
如果添加的新节点是文档中已有节点的话,则改变它的位置(将这个节点移动到最后)
//someNode有多个子节点
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false 第一个子节点已经改变
alert(returnedNode == someNode.lastChild); //true
insertrefore()
:插入节点,将要插入的节点放在父节点childNotes
的某个位置上,接收两个参数。参数一是要插入的节点,参数二是作为参照的节点。结果是要插入的节点将插入到参照节点之前(成为同胞节点,参数一是参数二的previousSibling
)。方法返回要插入的节点(即参数一)。
参数二为null时,和appendChild()
方法一样,插入到childNotes的最后。
//插入后成为最后一个子节点
returnedNode = someNode.insertrefore(newNode, null);
alert(returnedNode == someNode.lastChild); //true
//插入后成为第一个子节点
returnedNode = someNode.insertrefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true
//插入到最后一个子节点前面
returnedNode = someNode.insertrefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode.childNodes.length - 2]); //true
replaceChild()
:替换节点。接收两个参数,参数一是要插入到childNotes
中的节点(新节点),参数二是被替换的节点(旧节点)。被替换的节点将从文档树中被移除,由参数一代表的要插入的节点占据其位置(参数一替换参数二)。方法返回被替换的节点(参数二)。在使用replaceChild()
替换一个节点时,被替换的节点所有关系指针将被复制到要插入的节点上。从技术角度讲,被替换的节点还在文档中,但是在文档树中已经没有了它的位置。
//替换第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
//替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
removeChild()
:移除指定节点,返回被移除的节点。被remove的节点还在文档中,但是在文档树中已经没有了它的位置
//移除第一个子节点
var formerFirstChild = someNode.removeChild(someNode.firstChild);
//移除最后一个子节点
var formerLastChild = someNode.removeChild(someNode.lastChild);
上面介绍的四个方法操作的都是某个节点的子节点,也就是说,要使用这几个方法要先取得父节点,另外,不是所有的节点都有子节点,如果在不支持子节点的节点上调用了这些方法,将会导致错误发生。
其他方法
有两个方法是所有类型的节点都有的:cloneNode()
normalize()
。
cloneNode()
:创建一个调用这个方法的节点的一个副本。接收一个参数,参数值为true和false。true表示深复制,将复制整个节点和子节点树;false表示浅复制,只复制这个节点,不复制其子节点树。复制后返回的节点归文档所有,但没有为它指定父节点,因此这个节点副本就成了一个“孤儿”。除非通过appendChild()、insertrefore()或者replaceChild()方法将它添加到文档中。
假设有如下HTML代码:
<ul id="ul"><li>item1li><li>item2li><li>item3li>ul>
脚本:
var mylist = document.getElementryId("ul");
var deepList = mylist.cloneNode(true);
alert(deepList.childNodes.length); //3
var shallowList = mylist.cloneNode(false);
alert(shallowList.childNodes.length); //0
如果HTML代码如下:
<ul id="ul">
<li>item1li>
<li>item2li>
<li>item3li>
ul>
那么脚本代码结果将变为:
var mylist = document.getElementryId("ul");
var deepList = mylist.cloneNode(true);
alert(deepList.childNodes.length); //7
var shallowList = mylist.cloneNode(false);
alert(shallowList.childNodes.length); //0
之所以(deepList.childNodes.length
的值会发生改变,是因为IE(IE<9)和其他浏览器处理空白文本节点的机制不一样 ,FF,谷歌,IE>=9浏览器会将各个节点之间的空白也算作一个节点,但是IE(IE<9)不会。
解决方法一:像上面那样修改HTML源码节点之间无空格。
解决方法二:调用childNodes属性之前先将空格删除 (HTML源码可以缩进),脚本代码为:
var mylist = document.getElementryId("ul");
var deepList = mylist.cloneNode(true);
for (var i = 0; i < deepList.childNodes.length; i++) {
//如果是文本节点,并且值为空,则删除该节点
if (deepList.childNodes[i].nodeType == 3 && /\s/.test(deepList.childNodes[i].nodeValue)) {
deepList.childNodes[i].parentNode.removeChild(deepList.childNodes[i]);
}
}
alert(deepList.childNodes.length); //3
var shallowList = mylist.cloneNode(false);
alert(shallowList.childNodes.length); //0
nodeType
为常数3时表示节点为文本节点,第二个条件为使用正则表达式判断当前这个节点中是否含有空格(\s)。
normalize()
:这个方法的唯一作用就是处理文档树中的文本节点。由于解析器的实现或者DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点则删除它,如果找到了两个相邻的文本节点则将它们合并为一个文本节点。
JavaScript中通过Document类型表示文档。在浏览器中,document
对象是HTMLDocument(继承自Document类型)的第一个实例,表示整个HTML页面。而且,document
对象是window
对象的属性,因此可以将其作为全局对象来访问。Document节点具有以下特征:
nodeType
的值为9nodeName
的值为“#document”nodeValue
的值为nullparentNode
的值为nullownerDocument
的值为nullDocument类型可以表示HTML页面或者其他基于XML的文档。不过,最常见的应该是作为HTMLDocument实例的document对象。通过这个对象,不仅能够取得与页面有关的信息,还可以操作页面的外观和底层结构。
补充:在Firefox、Safari、Chrome和Opera中,可以通过脚本访问Document类型的构造函数和原型。但在所有浏览器中都可以访问HTMLDocument类型的构造函数和原型。
文档的子节点
虽然DOM标准规定Document节点的子节点可以是DocumentType、Element、ProcessingInstruction或Comment,但还有两个内置的访问其子节点的快捷方式,第一个就是documentElement
属性,该属性始终指向HTML页面中的元素。另一个就是通过
childNotes
列表访问文档元素,但通过documentElement
属性能更快捷、更直接的访问该元素。示例;
HTML代码:
<html lang="en">
<body>
body>
html>
脚本代码:
var html = document.documentElement;
alert(html === document.firstChild); //true
alert(html === document.childNodes[0]); //true
documentElement
虽然永远指向元素,但是
document.firstChild
和document.childNodes[0]
不一定。
document
还有一个body
属性,指向元素,开发人员经常要使用这个元素,因此
document.body
在JavaScript代码中出现的频率非常高。
var body = document.body;
所有浏览器都支持document.documentElement
和document.body
属性。
Document另一个可能的子节点是DocumentType。通常将标签看成一个与文档其他部分不同的实体,可以通过
doctype
属性(浏览器中是document.type
来访问它的信息):
vardoctype=document.doctype;
浏览器对document.doctype
的支持差别很大,可以给出如下总结:
1.IE8及之前版本:如果存在文档类型声明,会将其错误的解释为一个注释并把它当做Comment节点;而`document.doctype`的值始终为null。
2.IE9+及Firefox:如果存在文档类型声明,则将其作为文档的第一个子节点;`document.doctype`是一个DocumentType节点,也可以通过`document.firstChild`或者`document.childNodes[0]`访问同一个节点。
3.Safari、Chrome和Opera:如果存在文档类型声明,则将其解析,但不作为文档的子节点。`document.doctype`是一个DocumentType节点,但该节点不会出现在document.childNodes中。
由于浏览器对这个属性的支持不一致,因此这个属性的用处也很有限。
文档信息
作为HTMLDocument的一个实例,document
对象还有一些标准的Document对象所没有的属性。这些属性提供了document
对象所表现的一些网页的信息。其中第一个属性就是title
,包含着
元素的文本——显示在浏览器的标题栏或者标签页上。修改title的值就是修改
元素中文本的值,不会影响
元素。示例:
vartitleName=document.title;
alert(titleName);
document.title="修改后的title";//会反映在标签页或者标题栏上
接下来的3个属性都与浏览器请求有关,分别是URl
、domain
和referrer
。URL
属性包含页面完整的URL,domain
属性只包含页面的域名,而referrer
属性则保存着链接到当前页面的那个页面的URL。在没有来源页面的情况下,referrer
属性中可能存放的是空字符串。所有的这些信息都存储在HTTP请求头部,只不过通过这些属性让我们可以访问到它们而已,如下所示:
//获得完整的URL
var url = document.URL;
//取得域名
var domain = document.domain;
//取得来源页面的URL
var referrer = document.referrer;
URL
和domain
属性是相关联的。例如,如果document.URL
等于https://www.baicu.com/s,那么document.domain
就等于 www.baidu.com 。在这三个属性中,只有domain
是可以设置的,但出于安全方面的限制,也并非可以给domain
设置任何值。书中貌似可以给domain
属性设置值,但是我查了W3SCHOOL,上面指出该属性是只读的:
该属性是一个只读的字符串,包含了载入当前文档的 web 服务器的主机名。
来自 http://www.w3school.com.cn/jsref/prop_doc_domain.asp
为了验证,我写了一个试试:
<html>
<body>
本文档的域名是:
<script type="text/javascript">
document.write(document.domain);
document.domain = "w3school.com.cn";
document.write("
本文档的域名是:"+document.domain);
script>
body>
html>
运行结果:
本文档的域名是: www.w3school.com.cn
本文档的域名是:w3school.com.cn
这段代码并非在编辑器中所写,而是在W3SCHOOL中的示例代码中修改的。
书中说的是正确的,domain
属性确实可以修改,但是domain
不能设置为URL中不包含的域。
如下所示:
<html>
<body>
本文档的域名是:
<script type="text/javascript">
document.write(document.domain);
document.domain = "baidu.com.cn";
document.write("
本文档的域名是:"+document.domain);
script>
body>
html>
我修改了domain
设置的值,运行后结果:
本文档的域名是: www.w3school.com.cn
这说明domain
设置是有限制的(domain
设置的值必须在当前这个网站下,不可以设置为别的网站的域名)。
通过设置domain
属性,可以实现来自不同子域的页面之间的JavaScript通信。当页面中含有来自其他子域的框架或者内嵌框架时,由于跨域安全限制,来自不同子域的页面是无法通过JavaScript进行通信的,通过设置这些子域页面的domain
属性为相同的值,这些页面就可以互相访问对方包含的JavaScript对象了。举例:
有一个页面加载自www.baidu.com,其中包含一个内嵌框架,框架内的页面加载自p2p.baidu.com,由于
document.domain
中保存的字符串不同,内外两个页面之间无法访问对方的JavaScript对象,但如果将两个页面的domain
属性都设置为baidu.com,则它们之间就可以相互通信了。
浏览器对domain
属性还有一个限制,即如果域名一开始是“松散的”(loose),那么不能将它再设置为“紧绷的”(tight)。换句话说,将document.domain
属性设置为“baidu.com”之后,无法再将domain
属性设置为“www.baidu.com”,否则将会导致错误。如下所示:
//假设页面来自www.baidu.com
document.domain = "baidu.com"; //松散的(成功)
document.domain = "www.baidu.com"; //紧绷的(出错!)
所有的浏览器都存在这个限制,但IE8是最早实现这个限制的版本。
查找元素
查找元素的三个方法:
document.getElementryId();
document.getElementsByTagName();
document.getElementsByName();
document.getElementryId()
:接收一个参数,参数值为文档中元素节点的id特性值(Attribute),返回这个元素。参数id严格匹配,区分大小写(IE8及较低版本不区分大小写)。如果页面中多个元素的id值相同,则只返回文档中第一次出现的元素。IE7及较低版本还有一个怪癖:name特性与给定ID匹配的表单元素也会被该方法返回。
document.getElementsByTagName()
:接收一个参数,即元素的标签名,返回包含零或多个元素的NodeList。在HTML文档中,这个方法会返回一个HTMLCollection对象,作为一个“动态”集合,该对象与NodeList非常类似,可以使用方括号语法或者item()
方法来访问HTMLCollection中的项,同样具有length
属性。方括号中可以是数值索引和字符串索引(字符串必须为HTMLCollection中某个元素的name特性值)。HTMLCollection还有一个方法,叫做namedItem()
,使用这个方法可以通过元素的name
属性取得集合中的项。示例:
//获取所有标签元素
var images = document.getElementsByTagName("img");
//输出图片的数量
alert(images.length);
//输出第一个图像的src特性值
alert(images[0].src);
//输出第一个图像的src特性值
alert(images.item(0).src);
假设现在有这么一个元素:
<img src="" alt="" name="myImage">
在上面的“images”变量中取得这个元素:
var myImage = images.namedItem("myImage");
或者
var myImage = images["myImage"];
\对于HTMLCollection而言,我们可以向方括号中传入数值或者字符串形式的索引值,在后台,对数值索引就会调用item(),对字符串索引就会调用namedItem()。
向document.getElementsByTagName()
中传入一个“”表示通配符全部,即可以获取整个页面中所有的元素(按照先后顺序)。补充:IE将注释(Comment)实现为元素(Element),因此IE中传入“”会返回所有注释节点。传给document.getElementsByTagName()
中的参数是不区分大小写的。
document.getElementsByName()
:接收一个参数,参数值为页面中某个元素的name
特性值。最常使用在单选按钮中,操纵一组单选按钮,因此一组单选按钮的name
特性值一般相同。该方法也返回一个HTMLCollection,但是对于一组name
相同的元素来说,使用字符串索引或者调用namedItem()
方法则只会取得集合中的第一项(因为每一项的name特性值都相同)。
特殊集合
除了属性和方法,document
对象还有一些特殊的集合。这些集合都是HTMLCollection对象,为访问文档提供了快捷方式。
上面这些集合始终都可以通过HTMLDocument对象访问到,而且,和HTMLCollection对象类似,集合中的项也会随着当前文档内容的更新而更新。
DOM一致性检测
由于DOM分为多个级别,也包含多个部分,因此检测浏览器实现了哪些DOM标准就十分必要了。document.implementation
属性就是为此提供相应信息和功能的对象,它提供了一个hasFeature()
方法,该方法接收两个参数,参数一是DOM功能的名称,参数二是版本号。如果浏览器支持给定名称和版本的功能则返回true
,不支持则返回false
。示例:
var hasXMLDom = document.implementation.hasFeature("XML", "1.0");
alert(hasXMLDom); //true
document.implementation
属性貌似已经被弃用了……
https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature
文档写入
将输出流写入到网页。write()
、writeln()
、open()
、close()
。
document.write()
和document.writeln()
方法都接收一个字符串参数,write()
会原样输出,而writeln()
会在字符串的末尾添加一个换行符(\n)。在页面加载的过程中,可以向页面动态地加载内容:
<p>The current date and time is:
<script type="text/javascript">
document.write("" + new Date().toString() + "<\/b>>");
script>
p>
除此之外,还可以使用document.write()
和document.writeln()
方法动态地包含外部资源(如JS文件),在包含JS脚本时,要注意不能直接包含字符串,而是要将斜杠进行转义。否则将会导致字符串中的
解释为脚本代码结束,之后的代码将无法运行。示例;
The current date and time is:
<script type="text/javascript">
document.write("
浏览器解析文档时永远不会创建相邻的文本节点。
(在某些情况下,在IE6中使用normalize()
方法会导致浏览器崩溃)
分割文本节点
与上述能够合并文本节点的normalize()
方法相对的,Text类型有一个分割文本节点的方法——splitText()
,这个方法传入一个分割位置,即按照指定的位置分割nodeValue
值。原来的文本节点包含从开始到指定位置之前的内容,返回的新文本节点包含剩下的值。示例:
分割文本节点是从文本节点中提取数据的一种常用DOM解析技术。
注释在DOM中是通过Comment类型来表示的。Comment节点有以下特征:
nodeType
的值为8nodeName
的值为“#comment”nodeValue
的值是注释内容parentNode
可能是Document和ElementComment类型和Text类型继承自相同的基类,因此它拥有除splitText()
方法之外的所有字符串操作方法。与Text类型类似,也可以通过nodeValue
和data
属性来取得注释的内容。注释节点可以通过其父节点来访问。示例:
<body>
<div id="myDiv">div>
body>
<script type="text/javascript">
var div = document.getElementById("myDiv");
var comment = div.firstChild;
alert(comment.data); //"A Comment"
script>
还可以使用document.createComment()
方法创建一个注释节点,方法参数为注释文本。示例:
<script type="text/javascript">
var comment = document.createComment("A Comment");
var div = document.getElementById("myDiv");
div.appendChild(comment);
script>
开发人员很少会创建和访问注释节点,因为注释节点对算法鲜有影响。此外,浏览器也不会识别位于标签后面的注释。如果要访问注释节点,一定要保证该注释节点是
元素的后代。
CDATASection类型只针对基于XML的文档,表示的是CDATA区域。与Comment类似,CDATASection类型继承自Text类型,因此具有除splitText()
方法之外的所有字符串操作方法。CDATASection节点具有以下特征:
nodeType
的值为4nodeName
的值为“#cdata-section”nodeValue
的值是CDATA区域中的内容parentNode
可能是Document和ElementCDATA区域只会出现在XML文档中,因此多数浏览器都会把CDATA区域错误的解释为Comment或Element。示例:
<div id="myDiv">div>
这个例子中的 DocumentType类型在web浏览器中并不常用,仅有Firefox、Safari和Opera支持它(Chrome4.0也支持了)。DocumentType包含着与文档doctype有关的所有信息,它具有以下特征: 在DOM1级中,DocumentType对象不能动态创建,而只能通过解析文档代码的方式来创建。支持它的浏览器会将DocumentType对象保存在 示例中创建了一个严格型HTML4.01文档类型声明,DocumentType中name属性的值就是“HTML”,但是输出却是小写的“html”(没有找到具体的原因,但是只有H5的 在所有的节点类型当中,只有DocumentFragment在文档中没有对应的标记。DOM规定文档片段(document fragment)是一种“轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。DocumentFragment节点具有下列特征: 虽然不能直接将文档片段直接添加到文档中,但可以将它作为一个“仓库”来使用,即可以在里面保存将来可能会添加到文档中的节点。创建示例: 文档片段继承了所有Node的所有方法,通常用于执行那些针对文档的DOM操作。如果将文档中节点添加到文档片段中,被添加的节点将会在文档树中移除,在浏览器中也不会再看到该节点。添加到文档片段中的节点同样也不属于文档树。可以通过 简单的逻辑不需要特别说明,但是有一点:将文档片段中所有子节点添加到相应位置上(同时原本属于文档片段的所有子节点都会在被添加到文档树中后从文档片段中删除),文档片段本身永远不会成为文档树的一部分。这也是代码中两次弹窗的结果不一样的原因。 元素的特性在DOM中以Attr类型来表示。从技术角度讲,特性就是存在于元素的 尽管特性节点也是节点,但是却不被认为是DOM文档树的一部分。 不建议使用特性节点访问和设置特性,使用 动态脚本:动态脚本是指在页面加载时不存在,但将来的某一时刻通过修改DOM动态的添加脚本。跟操作HTML元素一样,创建动态脚本也有两种方式:插入外部文件和直接插入JavaScript代码。动态加载的脚本可以立即执行。示例: 使用一个函数动态加载某个外部js文件: 加载完成后,就可以在页面中其他地方使用这个脚本了,问题只有一个,怎么知道脚本加载完成呢?遗憾的是,并没有什么标准方式来探知这一点。不过,与此相关的一些事件可以派上用场,但要取决于使用的浏览器。 理论上讲,下面的代码是有效的: 上述代码可以在Chrome、Firefox、Safari、Opera中正确运行,但是在IE中却不可以,因为IE中将 注意:必须将 加载外部样式文件的过程是异步的,也就是加载样式和执行JavaScript代码的过程没有固定的次序。在第13章中可以学到利用事件来检测外部样式文件是否加载完成。 另一种定义样式的方式是嵌入式CSS: 和上面的引入外部样式一样,下列代码应该是有效的: 在五大主流浏览器中,只有IE会在执行上述代码时报错,IE将 一个通用的解决方案如下: 补充:在IE中重用同一个 为了构建表格,HTML DOM为 以下是为元素添加的属性和方法: 以下是为 为 使用上述API,可以简化DOM中对表格的操作,例如,现在有一个表格如下: 使用上述API插入这个表格: 理解NodeList及其“近亲”NamedNodeMap和HTMLCollection,是从整体上透彻理解DOM的关键所在。这三个集合都是“动态”的。每当文档结构发生变化时,它们都会得到更新,因此,它们始终都保存着最新、最准确的信息。从本质上说,所有NodeList对象都是在访问DOM文档时实时运行的查询。下列就是一个会导致无线循环的代码: 上述代码中,由于循环的判断条件中使用了NodeList对象,因此每次执行到此(判断)时,都会对文档运行一次查询,效率比较低。这样做的结果就是循环体中不断向body中添加div元素,因此divs.length不断增长,i也不断增长,成为一个死循环。解决方法如下: 解决方法就是将某一时刻的length属性保存到另一个变量中,这种方式更加保险。 理解DOM的关键,就是理解DOM对性能的影响。DOM操作往往是JavaScript程序中开销最大的部分,而因访问NodeList导致的问题为最多。document.createCDataSection()
方法来创建CDATA区域,只需向方法中传入节点的内容即可。
10.1.7 DocumentType类型
nodeType
的值为10nodeName
的值为doctype的名称nodeValue
的值为nullparentNode
是Documentdocument.doctype
中。DOM1级描述了DocumentType对象的三个属性:name、entities和notactions。其中,name表示文档类型的名称;entities是由文档类型描述的实体的NamedNodeMap对象;notations是由文档类型描述的符号的NamedNodeMap对象。通常,浏览器中的文档实用的都是HTML或者XHTML文档类型,因此entities和notations都是空列表(列表中的项来自行内文档类型声明)。但不管怎样,只有name属性是有用的。这个属性保存的是文档类型的名称,也就是出现在之后的文本
。示例:
<html>
<head>
<title>Titletitle>
head>
<body>
<script type="text/javascript">
alert(document.doctype.name); //"html"
script>
body>
html>
之后是“html”)
。
(IE的早期版本不支持DocumentType,因此document.doctype
的值始终都是null。可是,这些浏览器会将文档类型声明错误的解释为注释,并且为其创建一个注释节点。IE9会为document.doctype
赋正确的对象,但任然不支持访问DocumentType类型。)10.1.8 DocumentFragment类型
nodeType
的值为11nodeName
的值为”#document-fragment”nodeValue
的值为nullparentNode
是nullvar fragment = document.createDocumentFragment();
appendChild()
和insertBefore()
将文档片段中的内容添加到文档树中。使用这两个方法时,参数是DocumentFragment类型,但是其实现原理是将文档片段中所有子节点添加到相应位置上;文档片段本身永远不会成为文档树的一部分。示例:<body>
<ui id="myList">ui>
body>
<script type="text/javascript">
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;
for (var i = 0; i < 3; i++) {
li = document.createElement("li");
li.appendChild(document.createTextNode("Item:" + (i + 1)));
fragment.appendChild(li);
}
alert(fragment.childNodes.length); //3
ul.appendChild(fragment);
alert(fragment.childNodes.length); //0
script>
10.1.9 Attr类型
attributes
属性中的节点。特性节点具有以下特征:
nodeType
的值为11nodeName
的值是特性的名称nodeValue
的值是特性的值parentNode
的值为null
Attr对象有三个属性:name、value和specified。name是特性名称(与nodeName
的值相同),value是特性的值(与nodeValue
的值相同),而spe cified是一个布尔值,用以区别特性是在代码中指定的还是默认的。为某个元素添加一个特性,示例:<body>
<div id="element">MyDivdiv>
body>
<script type="text/javascript">
var element = document.getElementById("element");
var attr = document.createAttribute("align");
attr.value = "left";
element.setAttributeNode(attr); //将新创建的特性添加到元素中
alert(element.attributes.getNamedItem("align").value); //letf
alert(element.getAttributeNode("align").value); //left
alert(element.getAttribute("align")); //left
script>
getAttribute()
、setAttribute()
和removeAttribute()
更加简便。10.2 DOM操作技术
10.2.1 动态脚本
<script type="text/javascript" src="client.js">script>
<script type="text/javascript">
function loadScript(url) {
var script = document.createElement("script");
script.type = "text/javascript";
script.url = url;
document.body.appendChild(script);
}
//调用上述方法动态加载一个外部的js文件
loadScript("client.js");
script>
另一种指定JavaScript代码的方式是行内方式(直接插入行内)。示例:<script type="text/javascript">
function sayHi() {
alert("Hi!");
}
script>
var script = document.createElement("script");
script.type = "text/javascript";
script.appendChild(document.createTextNode("function sayHi() {alert('Hi!');}"));
document.body.appendChild(script);
元素视为特殊的元素,不允许DOM访问子节点,可以使用
元素的
text
属性来指定元素中包含的JavaScript代码。示例:var script = document.createElement("script");
script.type = "text/javascript";
//使用
元素添加到
而不是
中。
上面的整个过程可以使用一个函数来表示:<script type="text/javascript">
function loadStyles(url) {
var link = document.createElement("link");
link.rel = "stylesheet";
link.type ="text/css";
link.href = url;
var head = document.getElementsByTagNameNS("head")[0];
head.appendChild(link);
}
//调用上面的方法
loadStyles("style.css");
script>
<style type="text/css">
body {
background-color: red;
}
style>
<script type="text/javascript">
var style = document.createElement("style");
style.type = "text/css";
style.appendChild(document.createTextNode("body{background-color: red;}"));
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
script>
视为一个特殊的、和
类似的节点,不允许访问其子节点,解决办法就是在IE中该节点有一个
styleSheet
属性(貌似已经废弃),该属性又有一个cssText
属性可以接受CSS代码。示例:<script type="text/javascript">
var style = document.createElement("style");
style.type = "text/css";
try {
style.appendChild(document.createTextNode("body{background-color: red;}"));
} catch (ex) {
//貌似styleSheet已经废弃
//style.styleSheet.cssText = "body{background-color: red;}";
style.cssText = "body{background-color: red;}"; //正确执行
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
script>
<script type="text/javascript">
function loadStyleString(css) {
var style = document.createElement("style");
style.type = "text/css";
try {
style.appendChild(document.createTextNode(css));
} catch (ex) {
style.cssText = css;
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
}
//调用上面的方法
loadStyleString("body {background-color: red;}");
script>
元素并再次设置
styleSheet.cssText
属性时,会导致浏览器崩溃。10.2.3 操作表格
、
、
元素添加了一些属性和方法。
元素(如果有)的指针元素的HTMLCollection
元素的指针
元素的指针
元素,将其放入表格中,返回引用
元素,将其放入表格中,返回引用
元素,将其放入表格中,返回引用元素
元素
元素元素添加的属性和方法:
元素中行的HTMLCollection
元素添加的属性和方法:
元素中单元格的HTMLCollection
<body>
<table border="1" width="100%">
<tbody>
<tr>
<td>1td>
<td>2td>
tr>
<tr>
<td>3td>
<td>4td>
tr>
tbody>
table>
body>
10.2.4 使用NodeList
<script type="text/javascript">
var divs = document.getElementsByTagName("div"),
i,
div;
for (i=0;i
<script type="text/javascript">
var divs = document.getElementsByTagName("div"),
len,
i,
div;
for (i=0,len=divs.length;i
一般来说,应该尽量减少访问NodeList,因为每访问一次NodeList就会对文档进行一次查询。10.3 小结
你可能感兴趣的:(前端笔记,JavaScript笔记)