此前见过的绝大多数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。