动态创建标记

此前见过的绝大多数DOM方法只能用来查找元素。getElementById和getElementsByTagName都可以方便快捷地找到文档中的某个或某些特定的元素节点,这些元素随后可以用诸如setAttribute和nodeValue之类的方法和属性来处理。在这两种情况里,都是对已经存在的元素做出修改。这是绝大多数JavaScript函数的工作原理。网页的结构有标记负责,JavaScript函数只是来改变某些细节而不改变其底层结构。不过JavaScript也可以通过创建新元素和修改现有元素来改变网页结构。

一些传统方法

document.write

document对象的write()方法可以方便快捷地把字符串插入到文档里。 示例:

<!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内容。 例如下面一段代码:

<div id="testdiv">
  <p>This is <em>my</em> content.</p>
</div>
<div id="testdiv">
</div>

把下面这段JavaScript代码放入.js文件,就可以把一段HTML内容插入这个<div>标签:

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>标签内容变成空白:

<div id="testdiv">
</div>

我想把一段文本插入testdiv元素。这项任务需分两个步骤完成:

  • 创建一个新的元素

  • 把这个新元素插入节点树

第一个步骤要用DOM方法createElement来完成。

document.createElement(nodeName)

appendChild方法

appendChild方法是把创建的节点变成某个节点的子节点。

parent.appendChild(child)

创建一个p元素然后将其变成testdiv元素的一个子节点:

var para=document.createElement("p");
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);

createTextNode方法

你已经创建出了一个元素节点并把它插入了文档的节点树,这个节点是一个空白的p元素。你想把一些文本放入这个p元素,但createElement方法帮不上忙,它只能创建元素节点。而你需要创建一个文本节点,此时可以用createTextNode方法来实现它。

document.createTextNode(text)

最终代码:

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元素节点:

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文档

创建这些元素和设置各有关属性的工作:

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元素:

description.appendChild(desctxt)

最后是把新创建的元素插入文档:

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()方法,这个方法将把一个新元素插入到一个现有元素的前面。

parentElement.insertBefore(newElement,targetElement)
  • 新元素:你想插入的元素(newElement)

  • 目标元素:你想把这个新元素插入到哪个元素(targetElement)之前

  • 父元素:目标元素的父元素(parentElement)

**我们不必搞清楚父元素到底是哪个,因为targetElement元素的parentNode属性值就是它。**在DOM里,元素节点的父元素必须是另一个元素节点(属性节点和文本节点的子元素不允许是元素节点)。

在现有元素后插入一个新元素

DOM没有提供这样一个函数。

编写insertAfter函数

虽然DOM本身没有提供insertAfter方法,但它确实提供了把一个节点插入到另一个节点之后所需的所有工具。我们可以利用已有的DOM方法和属性编写一个insertAfter函数:

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方法)

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个不同的函数,分别是:

  • addLoadEvent函数

  • insertAfter函数

  • preparePlaceholder函数

  • prepareGallery函数

  • showPic函数

preparePlaceholder函数负责创建一个img元素和一个p元素。这个函数将把这些新创建的元素插入到节点树里图片库清单的后面。prepareGallery函数负责处理事件。这个函数将遍历处理图片库清单里的每个链接。当用户点击这些链接中的某一个时,就会调用showPic函数。showPic函数负责把“占位符图片”切换为目标图片。 下面是最终完成的showPic.js文件:

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属性。

<!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中创建新的对象要使用以下代码:

var request=new ActiveXObject("Msxml2.XMLHTTP.3.0");

更麻烦的是不同IE版本中使用的XMLHTTP对象也不完全相同。 其他浏览器则基于XMLHttpRequest来创建新对象:

var request=new XMLHttpRequest();

为了兼容所有浏览器,我们要写个函数getHTTPObject()达到目的:

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对象:

var request=getHTTPObject();

XMLHttpRequest对象有许多方法。其中最有用的是open方法,它用来指定服务器上将要访问的文件,指定请求的类型有:GET、POST和SEND。 例如:

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属性的值:

  • 0:未初始化

  • 1:正在加载

  • 2:加载完毕

  • 3:正在交互

  • 4:完成

只要readyState属性的值变成了4,就可以访问服务器发送回来的数据了。访问服务器发送回来的数据要通过两个属性完成。一个是responseText属性,用于保存文本字符串形式的数据。另一个是responseXML属性,用于保存Content-Type头部中指定为“text/xml”的数据,其实是一个DocumentFragment对象。

注意:在使用Ajax时,千万要注意同源策略。使用XMLHttpRequest对象发送的请求只能访问与其所在的HTML处于同一个域中的数据,不能向其他域发送请求。此外,有些浏览器还会限制Ajax请求使用的协议,比如Chrome。

异步请求有一个容易被忽略的问题是异步性,就是脚本在发送XMLHttpRequest请求之后,仍然会继续执行,不会等待响应返回。为此,如果其他脚本依赖于服务器的响应,那么就得把相应的代码都转移到指定给onreadystatechange属性的那个函数中。

渐进增强与Ajax

构建Ajax网站的最好方法,也是先构建一个常规的网站,然后Hijax它。

Hijax

Hijax是指渐进增强地使用Ajax。

Last updated