万维网联盟( W3C )在不同的规范组(DOM级别1,DOM级别2和DOM级别3)中定义了文档对象模型(DOM)。 DOM将HTML或XML文档表示为由具有属性和方法的节点层次结构组成的树。 使用JavaScript等客户端语言,您可以将事件添加,修改,删除并将事件附加到树中的节点,从而可以生成交互式动态网页。
使用客户端脚本(JavaScript)修改DOM称为DOM脚本。 使用DOM脚本来代替通用术语“动态HTML(DHTML)”,该术语已在Web开发中用于指示通过HTML,CSS和JavaScript构建的交互式Web页面。
在本文中,探索DOM API中最常用的方法和属性。 一个详细的示例显示了如何使用JavaScript遍历DOM。 一个更复杂的模型说明了在何处考虑事件和侦听器。 了解如何利用JavaScript库与DOM进行交互。
您可以下载本文中使用的源代码。 相关主题为希望深入了解本文中讨论的概念的人员提供了链接。
DOM脚本
在DOM术语中,文档表示为树的根。 在JavaScript中,它是window.document
,或者仅仅是document(因为它附加到Window对象)。 这是某些JavaScript实现的起点。 清单1显示了一个HTML片段的示例。
清单1. HTML代码
<body>
<p id="paragraph1">
<span>This is some text</span>
<a href="/index.html" title="Click here">Click here</a>
<p>
</body>
从DOM的角度来看,在上面的示例中, p
标签由DOM Element
接口表示。 它是span
标记和a
标记的父代。 该span
和a
标签是兄弟姐妹。
假设您想在清单1的代码中获取锚点的href
属性。 访问DOM中元素的一种简单方法是使用getElementById
方法。 以下代码字符串显示了包含以接口定义语言(IDL)编写的getElementById
签名的文档接口定义的一部分: Element getElementById (in DOMString elementId)
。
JavaScript使用String
对象实现DOMString
接口,因此该方法接受元素id作为字符串形式的参数。 在示例片段中,唯一具有id
属性的元素是p
标签,因此可以使用var paragraph = document.getElementById("paragraph1");
进行检索var paragraph = document.getElementById("paragraph1");
。
您可以使用childNodes
属性获取嵌套在p
标签中的锚点。 此属性属于Node
接口,并返回NodeList
类型的对象。 该对象是JavaScript中类似数组的对象。 类似数组的对象没有诸如pop()
或push()
,但是它们具有length
属性。 从childNodes
属性返回的对象在节点元素(HTML标记),文本节点或注释节点之间没有任何区别。 如果仅查找节点元素,则可以考虑children
属性。 在不考虑文本和注释节点的情况下,它的性能要优于childNodes
。 在该示例中,锚点是该段落的第二个子var aElement = paragraph.children[1];
,可以通过以下方式获得: var aElement = paragraph.children[1];
。
给定一个元素,要获取href
属性的值,可以通过将属性名称作为参数传递(在本例中为href
)来采用getAttribute
方法。 IDL定义中包含getAttribute方法的部分是: DOMString getAttribute (in DOMString name)
。
在示例中,您可以像这样实现上述接口: var aHref = aElement.getAttribute("href"); // "index.html"
var aHref = aElement.getAttribute("href"); // "index.html"
。
与JavaScript中一样,您可以链接方法。 要仅在一行中获得a
标签的href
属性值,请使用: var aHref = document.getElementById("paragraph1").children[1].getAttribute("href"); // index.html */
var aHref = document.getElementById("paragraph1").children[1].getAttribute("href"); // index.html */
。
深入研究DOM脚本:示例应用程序
本节探讨DOM脚本编写中的一些功能。 示例便笺应用程序是一个交互式网页,使用户无需重新加载页面即可添加“便笺”便笺。 图1显示了该页面。
图1. Sticky Notes应用程序前端
![即时贴应用程序前端](https://i-blog.csdnimg.cn/blog_migrate/de10c54577b122f543780a224c2daea1.png)
清单2中显示了图1中显示的页面HTML代码。 head
标签内是对CSS和JS文件的引用。 在body
标记中,您可以看到页面中已有注释的结构: textarea
标记和触发创建新注释的锚点。
清单2. HTML代码
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8">
<title>
Dom Scripting
</title>
<link rel="stylesheet" href="css/master.css" />
<script src="js/script.js"></script>
</head>
<body>
<div class="wrapper">
<h1> Sticky Notes </h1>
<div class="links">
<textarea id="contentArea" cols="10"> </textarea>
<a href="/random.html" class="add">Click here</a>
<span>to add a sticky note</span>
</div>
<div id="notes">
<div class="note">
<p>
This is a note
</p>
</div>
</div>
</div>
</body>
</html>
让我们分析该页面加载的script.js文件中包含JavaScript代码。 一旦页面加载或文档构建完成,就需要触发脚本的逻辑。 为此,一种选择是将一个函数绑定到onload
window属性,如清单3所示。
清单3. onload
属性
window.onload = init;
function init() {}
onload
属性与DOM事件负载相关联,这是在DOM级别0(所有浏览器均支持的“规范”,但不是标准)下如何将事件绑定到侦听器功能的典型方法。 相反,在DOM级别2规范中定义了标准的DOM事件模型。 在此规范中,定义了addEventListener
方法(来自EventTarget
接口)以在目标元素上注册事件处理程序。 以下代码公开了此方法的签名: object.addEventListener(eventType, eventHandler, useCapture);
。
其中eventType
是要在对象上注册的事件,而eventHandler
是绑定到特定事件的函数。 useCapture
是一个可选的布尔值,用于定义函数将在事件流的哪个阶段被调用(冒泡或捕获)。 以下代码使用addEventListener
函数将load方法绑定到窗口: window.addEventListener("load", init, false);
。
不幸的是,版本9之前的Internet Explorer(IE)不支持上述W3C方法,并且具有自己的实现: object.attachListener(eventType, eventHandler);
。 请参阅相关主题 ,以了解有关DOM Level 3的事件IE支持信息。
eventType
需要在事件名称on
加上前缀。 默认情况下,IE气泡中的事件,因此不存在useCapture
参数。
清单4取自script.js,显示了addEvent
函数,该函数处理所有浏览器中的事件绑定。 它是称为SA的全局对象的方法。 该方法适用于前面讨论的所有方法。
清单4. addEvent
函数
window.SA = {
addEvent : function(element, evType, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener (evType, fn, useCapture);
return true;
} else if (element.attachEvent) {
var r = element.attachEvent('on' + evType, fn);
return r;
} else {
element['on' + evType] = fn;
}
}
}
如果使用addEvent
函数,则可以将一个函数(称为SA.load
)绑定到load
事件,如清单5所示。
清单5.绑定函数
SA.addEvent(window, "load", SA.load, false);
SA = {
...
load : function() {
// init block
}
}
上面的SA.load
函数仅在下载所有资源后才触发,因为它附加到load
事件。 在一般情况下,附加到load事件的函数可能需要一段时间才能执行,尤其是在页面中有许多图像要下载的情况下。 将初始化脚本的功能附加到DOMContentLoaded
事件是一个好习惯,该事件在现代浏览器中受支持,并在构建DOM时触发。 该功能将在下载外部资源之前执行,从而使页面更具响应性。 在版本9之前,IE没有开箱即DOMContentLoaded
包含DOMContentLoaded
事件,因此需要一种变通方法以使其像其他浏览器一样工作。 在此示例中,页面中没有任何图像,因此您可以保持加载方式(页面的性能不会受到很大影响)。
现在您可以将函数处理程序与目标锚点上的click事件相关联。 当用户单击锚点时,将执行特定的行为。 在示例中,将创建一个新注释。 第一个任务是遍历DOM以检索我们定位的锚点,如清单6所示。
清单6.检索到具有类名add
锚点
load : function() {
var anchorSelected;
if (document.getElementsByClassName) {
anchorSelected = document.getElementsByClassName("add")[0];
} else {
var anchors = document.getElementsByTagName("a"),
alenght = anchors.length;
for (var i = 0; i < alenght; i++ ) {
var anchor = anchors[i];
if (anchor.className === "add") {
anchorSelected = anchor;
}
}
}
}
在清单6中,您可能可以预测到document.getElementsByClassName
方法使您可以检索具有给定类名的元素。 此方法返回HTML元素的集合,但是不幸的是,并非所有浏览器(例如IE6和IE7)都完全支持此方法。 对于那些浏览器,需要编写不同的逻辑。 您可以首先通过document.getElementsByTagName
方法获取锚点列表,然后遍历该列表以使用名为add
CSS类获得锚点。 GetElementsByTagName
方法正式返回一个NodeList
对象,幸运的是,所有主要浏览器都完全支持该方法。
在清单6中,您将看到如何在alength
变量中存储锚点数组的大小,以便在for循环中仅查询DOM一次。 修改和使用DOM是一项昂贵的操作,因此您应尽量减少与DOM交互的次数。
此时,一旦检索到锚点,就可以将click
事件绑定到负责添加注释的侦听器函数,如清单7所示。
清单7.绑定事件
load : function() {
...
SA.addEvent(anchorSelected, "click", SA.addNote, false);
}
清单7显示了附加到click事件的事件侦听器称为SA.addNote
。 此功能有几个目标:
- 克隆最新创建的笔记
- 将用户键入的文本注入刚刚克隆的笔记中
- 将新笔记追加到笔记列表中
清单8显示了实现第一个目标的实现。
清单8.克隆最新创建的笔记
addNote : function(event) {
var notes = document.getElementById("notes");
// Clone the node
var newNode = notes.children[0].cloneNode(true);
},
通过getElementById
方法获取具有注释ID的div
标签后,您将检索嵌套在div
的第一个子级,并使用cloneNode
方法对其进行克隆。 将刚刚克隆的DOM节点存储在名为newNode
的变量中。
选择嵌套在newNode
内的段落节点,在克隆的节点上调用getElementsByTagName
方法。 DOM提供了一个名为textContent
的属性来获取节点的内容。 不幸的是,并非所有浏览器都完全支持它。 您需要采用另一种方法:在该段中,访问firstChild
属性,然后firstChild
检索nodeValue
属性。 现在,使用页面中存在的textarea
标记的内容设置刚刚获得的nodeValue
。 textarea
的内容来自通过getElementById
方法实现的textarea
DOM元素的value属性。 清单9显示了如何将用户键入的文本注入到刚刚克隆的笔记中(第二个目标)。
清单9.将文本区域中的文本注入刚刚克隆的笔记中
addNote : function(event) {
...
// Set the content of the node
newNode.getElementsByTagName("p")[0].firstChild.nodeValue =
document.getElementById("contentArea").value;
notes.appendChild(newNode);
}
对于最后一个目标,使用appendChild
方法将创建的新笔记追加到笔记列表中,如清单10所示。
清单10.将新注释追加到列表
addNote : function(event) {
...
notes.appendChild(newNode);
}
最后,您需要防止click事件的默认行为(对于锚点,默认行为是将用户重定向到href
属性中指定的URL)。 DOM通过将参数传递给处理函数来指定preventDefault()
方法来完成此任务,将其应用于事件。 同样,在版本9之前的IE中不支持此方法。要实现相同的目标,在9版之前的IE中,可以将event.returnValue
属性设置为false。 清单11显示了代码。
清单11.防止click事件的默认行为
addNote : function(event) {
...
event.preventDefault ? event.preventDefault() : event.returnValue = false;
}
清单12显示了script.js文件中包含的所有JavaScript代码。
清单12. Script.js
window.SA = {
addEvent : function(element, evType, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener (evType, fn, useCapture);
return true;
} else if (element.attachEvent) {
var r = element.attachEvent('on' + evType, fn);
return r;
} else {
element['on' + evType] = fn;
}
},
load : function() {
var anchorSelected;
if (document.getElementsByClassName) {
anchorSelected = document.getElementsByClassName("add")[0];
} else {
var anchors = document.getElementsByTagName("a"),
alenght = anchors.length;
for (var i = 0; i < alenght; i++ ) {
var anchor = anchors[i];
if (anchor.className === "add") {
anchorSelected = anchor;
}
}
}
SA.addEvent(anchorSelected, "click", SA.addNote, false);
},
addNote : function(event) {
var notes = document.getElementById("notes");
// Clone the node
var newNode = notes.children[0].cloneNode(true);
// Set the content of the node
newNode.getElementsByTagName("p")[0].firstChild.nodeValue
= document.getElementById("contentArea").value;
notes.appendChild(newNode);
event.preventDefault ? event.preventDefault() : event.returnValue = false;
}
}
SA.addEvent(window, "load", SA.load, false);
JavaScript库和DOM
当开发人员编写JavaScript代码时,他们经常使用JavaScript库或框架,这些库或框架在不同的浏览器中处理DOM的不同实现。 注意,如何使用流行的jQuery库重写清单13 。
清单13. Script-jquery.js
$(function(){
$('a.add').click(function(){
var newNote = $('.note').eq(0).clone();
newNote.find('p').text($('#contentArea').val());
$('#notes').append(newNote);
return false;
});
});
保存了几行代码,代码简洁整洁。
因为需要使用HTML导入库才能使用jQuery,如清单14所示,所以将发出一个附加的HTTP请求,并且将需要更多时间来执行该库。 此过程可能会使应用程序变慢,因此由您来决定如何在编写较少代码的情况下平衡使用库。
清单14.将库导入HTML
<html>
<head>
...
<script src="//ajax.googleapis.com/ajax/libs/jquery
/1.6.1/jquery.min.js"></script>
</head>
JavaScript库是非常强大的工具,可以使您的生活更轻松。 但是,您需要了解DOM脚本,因为使用库并不总是处理DOM的最有效方法。 还建议您了解图书馆幕后发生的事情。
结论
DOM对Web开发人员很重要,因为它是JavaScript访问网页的方式。 浏览器供应商实施DOM API的方式存在一些问题和限制。 并非所有浏览器都完全支持某些属性和方法(例如addEventListener()
, textContent
),或者在某些情况下它们的行为有所不同。
性能是DOM脚本要考虑的另一个重要因素。 如本文所述,只要您知道JavaScript和DOM的交互方式,就可以利用某些JavaScript框架来操纵和遍历DOM。
翻译自: https://www.ibm.com/developerworks/web/library/wa-jsdomupdate/index.html