此前见过的绝大多数DOM方法只能用来查找元素。getElementById和getElementsByTagName都可以方便快捷地找到文档中的某个或某些特定的元素节点,这些元素随后可以用诸如setAttribute和nodeValue之类的方法和属性来处理。在这两种情况里,都是对已经存在的元素做出修改。这是绝大多数JavaScript函数的工作原理。网页的结构有标记负责,JavaScript函数只是来改变某些细节而不改变其底层结构。不过JavaScript也可以通过创建新元素和修改现有元素来改变网页结构。
一些传统方法
document.write
document对象的write()方法可以方便快捷地把字符串插入到文档里。 示例:
Copy <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Test</title>
</head>
<body>
<script>
document.write("<p>This is inserted.</p>");
</script>
</body>
</html>
将这个html文件加载到web浏览器,你将看到“This is inserted.”的文本段落。 document.write的最大缺点是它违背了“行为应该与表现分离”的原则。从某种意义上讲,使用document.write方法有点像使用<font>标签去设定字体和颜色。虽然这两种技术在HTML文档里都工作得不错,但它们不够优雅。把结构、行为和样式分开永远是一个好主意。只要有可能,就应该用外部CSS文件代替<font>标签去设定和管理网页的样式信息,最好用外部JavaScript文件去控制网页的行为。应该避免在<body>部分乱用<script>标签,避免使用document.write方法。
innerHTML属性
innerHTML属性可以用来读、写某给定元素里的HTML内容 。 例如下面一段代码:
Copy <div id="testdiv">
<p>This is <em>my</em> content.</p>
</div>
Copy <div id="testdiv">
</div>
把下面这段JavaScript代码放入.js文件,就可以把一段HTML内容插入这个<div>标签:
Copy window.onload=function(){
var testdiv=document.getElementById("testdiv");
testdiv.innerHTML="<p>I inserted <em>this</em> content.</p>";
}
testdiv元素里有没有HTML内容无关紧要:一旦你使用了innerHTML属性,它的内容就将全部替换。 在需要把一大段HTML内容插入一份文档时,innerHTML属性可以让你又快又简单地完成这一任务。不过,innerHTML属性不会返回任何对刚插入内容的引用。如果想对那些内容进行处理,则需要使用DOM提供的那些精确的方法和属性。
DOM方法
DOM是文档的表示。DOM所包含的信息与文档里的信息一一对应。它是一条双向车道,不仅可以获取文档的内容,还可以更新文档的内容。例如setAttribute方法,用这个方法可以改变文档的某个属性节点,不过setAttribute方法并未改变文档的物理内容,如果用文本编辑器打开,我们将看不到任何变化,这是因为浏览器实际显示的是那颗DOM节点树。在浏览器看来,DOM节点树才是文档 一旦明白这个,以动态方式实时创建标记就不那么难理解了。你并不是在创建标记,而是在改变DOM节点树。
createElement方法
还是让id等于testdiv的那个<div>标签内容变成空白:
Copy <div id="testdiv">
</div>
我想把一段文本插入testdiv元素。这项任务需分两个步骤完成:
第一个步骤要用DOM方法createElement来完成。
Copy document.createElement(nodeName)
appendChild方法
appendChild方法是把创建的节点变成某个节点的子节点。
Copy parent.appendChild(child)
创建一个p元素然后将其变成testdiv元素的一个子节点:
Copy var para=document.createElement("p");
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);
createTextNode方法
你已经创建出了一个元素节点并把它插入了文档的节点树,这个节点是一个空白的p元素。你想把一些文本放入这个p元素,但createElement方法帮不上忙,它只能创建元素节点。而你需要创建一个文本节点,此时可以用createTextNode方法来实现它。
Copy document.createTextNode(text)
最终代码:
Copy window.onload=function(){
var para=document.createElement("p");
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);
var txt=document.createTextNode("hello world");
para.appendChild(txt);
}
或者换种思路,先将文本节点追加到p元素节点,然后再把p元素节点追加到文档中的testdiv元素节点:
Copy window.onload=function(){
var para=document.createElement("p");
var txt=document.createTextNode("hello world");
para.appendChild(txt);
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);
}
重回图片库
回顾案例研究:JavaScript图片库 gallery.html文件中的标记(可以将链接文字改为缩略图)。这个文件有一个图片(placeholder)和一段文字(description)仅仅是位showPic脚本服务的。既然这些元素的存在只是为了让DOM方法处理它们,那么用DOM方法来创建它们才是最合适的选择。 先把这些元素从gallery.html中删除。然后编写一个函数preparePlaceholder并把它放进showPic.js文件,然后在文档加载时调用这个函数。下面是这个函数要完成的任务: 1.创建一个img元素节点 2.设置这个节点的id属性 3.设置这个节点的src属性 4.设置这个节点的alt属性 5.创建一个p元素节点 6.设置这个节点的id属性 7.创建一个文本节点 8.把这个文本节点追加到p元素上 9.把p元素和img元素插入到gallery.html文档
创建这些元素和设置各有关属性的工作:
Copy var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.jpg");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desctxt=document.createTextNode("choose an image");
把创建的文本节点插入p元素:
Copy description.appendChild(desctxt)
最后是把新创建的元素插入文档:
Copy document.getElementsByTagName("body")[0].appendChild(placeholder);
document.getElementsByTagName("body")[0].appendChild(description);
//也可以使用HTML-DOM提供的属性body
document.body.appendChild(placeholder);
document.body.appendChild(description);
以上代码工作得很好,但这一切都依赖于一个细节:图片清单(<ul>...</ul>)刚好是<body>部分的最后一个元素。但如果这个图片清单的后面还要一些其他的内容,而我们想把新创建的元素紧跟在图片清单后面怎么办呢?
在已有元素前插入一个新元素
DOM提供了名为insertBefore()方法,这个方法将把一个新元素插入到一个现有元素的前面。
Copy parentElement.insertBefore(newElement,targetElement)
目标元素:你想把这个新元素插入到哪个元素(targetElement)之前
父元素:目标元素的父元素(parentElement)
**我们不必搞清楚父元素到底是哪个,因为targetElement元素的parentNode属性值就是它。**在DOM里,元素节点的父元素必须是另一个元素节点(属性节点和文本节点的子元素不允许是元素节点)。
在现有元素后插入一个新元素
DOM没有提供这样一个函数。
编写insertAfter函数
虽然DOM本身没有提供insertAfter方法,但它确实提供了把一个节点插入到另一个节点之后所需的所有工具。我们可以利用已有的DOM方法和属性编写一个insertAfter函数:
Copy function insertAfter(newElement,targetElement){
var parent=targetElement.parentNode;
if(parent.lastChild==targetElement){
parent.appendChild(newElement);
}else{
parent.insertBefore(newElement,targetElement.nextSibling);
}
}
targetElement.nextSibling是目标元素的下一个兄弟元素。
使用insertAfter函数
使用insertAfter函数后preparePlaceholder函数现在的样子:(记住测试浏览器是否支持这些DOM方法)
Copy function preparePlaceholder(){
if(!document.createElement) return false;
if(!document.createTextNode) return false;
if(!document.getElementById) return false;
if(!document.getElementById("imagegallery")) return false;
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.jpg");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desctxt=document.createTextNode("choose an image");
description.appendChild(desctxt);
var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
insertAfter(description,placeholder);
}
图片库二次改进版
现在showPic.js文件包含5个不同的函数,分别是:
preparePlaceholder函数负责创建一个img元素和一个p元素。这个函数将把这些新创建的元素插入到节点树里图片库清单的后面。prepareGallery函数负责处理事件。这个函数将遍历处理图片库清单里的每个链接。当用户点击这些链接中的某一个时,就会调用showPic函数。showPic函数负责把“占位符图片”切换为目标图片。 下面是最终完成的showPic.js文件:
Copy function addloadEvent(func){
var oldonload=window.onload;
if(typeof window.onload!='function'){
window.onload=func;
}else{
window.onload=function(){
oldonload();
func();
}
}
}
function insertAfter(newElement,targetElement){
var parent=targetElement.parentNode;
if(parent.lastChild==targetElement){
parent.appendChild(newElement);
}else{
parent.insertBefore(newElement,targetElement.nextSibling);
}
}
function preparePlaceholder(){
if(!document.createElement) return false;
if(!document.createTextNode) return false;
if(!document.getElementById) return false;
if(!document.getElementById("imagegallery")) return false;
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.jpg");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desctxt=document.createTextNode("choose an image");
description.appendChild(desctxt);
var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
insertAfter(description,placeholder);
}
function prepareGallery(){
if(!document.getElementsByTagName) return false;
if(!document.getElementById) return false;
if(!document.getElementById("imagegallery")) return false;
var gallery=document.getElementById("imagegallery");
var links=gallery.getElementsByTagName("a");
for(var i=0;i<links.length;i++){
links[i].onclick=function(){
return showPic(this)?false:true;
}
}
}
function showPic(whichpic){
if(!document.getElementById("placeholder")) return false;
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
if(placeholder.nodeName!="IMG") return false;
placeholder.setAttribute("src",source);
if(document.getElementById("description")){
var text=whichpic.getAttribute("title")?whichpic.getAttribute("title"):"";
var description=document.getElementById("description");
description.firstChild.nodeValue=text;
}
return true;
}
addloadEvent(preparePlaceholder);
addloadEvent(prepareGallery);
JavaScript代码增加了,但标记相应的减少了。gallery.html文件现在只包含一个由JavaScript脚本和CSS样式表共用的“挂钩”。这个“挂钩”就是图片清单的id属性。
Copy <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Image Gallery</title>
<link rel="stylesheet" href="styles/layout.css" media="screen"/>
</head>
<body>
<h1>Snapshots</h1>
<ul id="imagegallery">
<li>
<a href="images/fireworks.jpg" title="a firework display">fireworks</a>
</li>
<li>
<a href="images/coffee.png" title="a cup of coffee">coffee</a>
</li>
<li>
<a href="images/rose.jpg" title="a red rose">rose</a>
</li>
<li>
<a href="images/bigben.jpeg" title="the famouse clock">bigben</a>
</li>
</ul>
<script src="scripts/showPic.js"></script>
</body>
</html>
Ajax
以前,web应用都要涉及大量的页面刷新:用户点击了某个链接,请求发送回服务器,然后服务器根据用户的操作再返回新页面。即便用户看到的只是页面的一小部分发生变化,也要刷新和重新加载整个页面。使用Ajax就可以做到只更新页面中的一小部分。 Ajax的主要优势就是对页面的请求以异步方式发送到服务器,而服务器不会用整个页面来响应请求它会在后台处理,与此同时用户还能继续浏览页面并与页面交互。你的脚本则可以按需加载和创建页面内容,而不会打断用户的浏览体验。
XMLHttpRequest对象
Ajax技术的核心就是XMLHttpRequest对象。这个对象充当着浏览器中的脚本(客户端)与服务器之间的中间人的角色。以往的请求都由浏览器发出,而JavaScript通过这个对象可以自己发出请求,同时也自己处理响应。 创建XMLHttpRequest对象:微软最早在IE5中以ActiveX对象的形式实现了一个名叫XMLHTTP的对象。在IE中创建新的对象要使用以下代码:
Copy var request=new ActiveXObject("Msxml2.XMLHTTP.3.0");
更麻烦的是不同IE版本中使用的XMLHTTP对象也不完全相同。 其他浏览器则基于XMLHttpRequest来创建新对象:
Copy var request=new XMLHttpRequest();
为了兼容所有浏览器,我们要写个函数getHTTPObject()达到目的:
Copy function getHTTPObject(){
if (typeof XMLHttpRequest=="undefined")
XMLHttpRequest=function(){
try {return new ActiveXObject("Msxml2.XMLHTTP.6.0");}
catch(e){}
try{return new ActiveXObject("Msxml2.XMLHTTP3.0");}
catch(e){}
try{return new ActiveXObject("Msxml2.XMLHTTP");}
catch(e){}
return false;
}
return new XMLHttpRequest();
}
getHTTPObject通过对象检测技术使其能在不同浏览器实现兼容。用getHTTPObject函数创建XMLHttpRequest对象:
Copy var request=getHTTPObject();
XMLHttpRequest对象有许多方法。其中最有用的是open方法,它用来指定 服务器上将要访问的文件,指定请求的类型有:GET、POST和SEND。 例如:
Copy function getNewContent(){
var request=getHTTPObject();
if(request){
request.open("GET","example.txt",true); //第三个参数用于指定请求是否以异步方式发送和处理
request.onreadystatechange=function(){
if(request.readyState==4){
var para=document.createElement("p");
var txt=document.createTextNode(request.responseText);
para.appendChild(txt);
document.getElementById('new').appendChild(para);
}
};
request.send(null);
}else{
alert('Sorry,your browser does not support XMLHttpRequest');
}
}
当页面加载完成后,以上代码会发起一个GET请求,请求与.html文件位于同一目录的example.txt文件。代码中onreadystatechange是一个事件处理函数,它会在服务器给XMLHttpRequest对象送回响应的时候被触发执行。在指定了请求的目标,也明确了如何响应之后,就可以用send方法来发送请求了:request.send(null);不发送任何数据的请求。 服务器在向XMLHttpRequest对象发回响应时,该对象有许多属性可用,浏览器会在不同阶段更新readyState属性的值:
只要readyState属性的值变成了4,就可以访问服务器发送回来的数据了。访问服务器发送回来的数据要通过两个属性完成。一个是responseText属性,用于保存文本字符串形式的数据。另一个是responseXML属性,用于保存Content-Type头部中指定为“text/xml”的数据,其实是一个DocumentFragment对象。
注意:在使用Ajax时,千万要注意同源策略。使用XMLHttpRequest对象发送的请求只能访问与其所在的HTML处于同一个域中的数据,不能向其他域发送请求。此外,有些浏览器还会限制Ajax请求使用的协议,比如Chrome。
异步请求有一个容易被忽略的问题是异步性,就是脚本在发送XMLHttpRequest请求之后,仍然会继续执行,不会等待响应返回。为此,如果其他脚本依赖于服务器的响应,那么就得把相应的代码都转移到指定给onreadystatechange属性的那个函数中。
渐进增强与Ajax
构建Ajax网站的最好方法,也是先构建一个常规的网站,然后Hijax它。
Hijax
Hijax是指渐进增强地使用Ajax。