JS也可以用來改變網(wǎng)頁的結(jié)構(gòu)和內(nèi)容
一些傳統(tǒng)方法
document.write
document對象的write()方法可以方便快捷地把字符串插入到文檔里。
如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<script type="text/javascript">
document.write("<p>This is inserted.</p>");
</script>
</body>
</html>

document.write的最大缺點(diǎn)就是違背了行為應(yīng)該與表現(xiàn)分離的原則。即使把document.write語句挪到外部函數(shù)里,也還是需要在標(biāo)記的<body>部分使用<script>標(biāo)簽才能調(diào)用那個函數(shù)。
例如:
function insertParagraph(text){
var str="<p>";
str+=text;
str+="</p>";
document.write(str);
}
-----------------------------
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<script type="text/javascript" src="example.js"></script>
<script type="text/javascript">
insertParagraph("This is inserted.");
</script>
</body>
</html>
PS:上面的這種寫法缺點(diǎn)有,很容易導(dǎo)致驗(yàn)證錯誤。MIME類型application/xhtml+xml與document.write不兼容,瀏覽器在呈現(xiàn)這種XHTML文檔時根本不會執(zhí)行document.write方法。
innerHTML屬性
現(xiàn)如今的瀏覽器幾乎都支持屬性innerHTML,這個屬性不是W3C DOM標(biāo)準(zhǔn)的組成部分,但現(xiàn)在已經(jīng)包含到HTML5規(guī)范中。innerHTML屬性可以用來讀寫某個給定元素的HTML內(nèi)容。
舉個例子就很容易理解了。
<div id="testdiv">
<p>This is <em>my</em>content.</p>
</div>

從innerHTML屬性的角度來看,id為testdiv的標(biāo)記里面只有一個值為<p>This is <em>my</em>content.</p>的HTML字符串。
window.onload=function(){
var testdiv=document.getElementById("testdiv");
alert(testdiv.innerHTML);
}
很明顯,innerHTML屬性無細(xì)節(jié)可言,要想獲得細(xì)節(jié),就必須使用DOM方法和屬性。標(biāo)準(zhǔn)化的DOM像手術(shù)刀一樣精細(xì),innerHTML屬性就像一把大錘那樣粗放。
當(dāng)你需要把一大段HTML內(nèi)容插入網(wǎng)頁時,innerHTML屬性更適用,它既支持讀取,又支持寫入,你不僅可以用它來讀出元素的HTML內(nèi)容,還可以用它把HTML內(nèi)容寫入元素。
window.onload=function(){
var testdiv=document.getElementById("testdiv");
testdiv.innerHTML="<p>I inserted<em>this</em>content.</p>";
}
利用這個技術(shù)無法區(qū)分插入和替換一段HTML內(nèi)容,一旦使用了innerHTML屬性,它的全部內(nèi)容將會被替換。innerHTML屬性時HTML專有屬性,不能用于任何其他標(biāo)記語言文檔。瀏覽器在呈現(xiàn)正宗的XHTML文檔時會直接忽略掉innerHTML屬性。
PS:在任何時候,標(biāo)準(zhǔn)的DOM都可以用來替換innerHTML。
DOM方法
DOM是文檔的表示。DOM所包含的信息與文檔里的信息一一對應(yīng)。DOM是一條雙向的車道。不僅可以獲取文檔的內(nèi)容,還可以更新文檔的內(nèi)容。
createElement方法
通過例子說明方法。
以下文檔里,想把一段文本插入到testdiv元素。
<div id="testdiv">
</div>
用DOM的語言來說,就是想添加一個p元素節(jié)點(diǎn)。目前div已經(jīng)又一個屬性節(jié)點(diǎn)(子節(jié)點(diǎn))。任務(wù)分為兩步:
- 創(chuàng)建一個新的元素 document.createElement(nodeName)
- 把這個新元素插入節(jié)點(diǎn)樹
實(shí)際操作過程如下:
var para=document.createElement("p");
//雖然創(chuàng)建出來p元素的引用,但它還不是任何一顆DOM節(jié)點(diǎn)樹的組成部分。這種情況稱為文檔碎片(document fragment)
通過para.nodeName查看節(jié)點(diǎn)名稱
通過para.nodeType查看節(jié)點(diǎn)類型
appendChild方法
把新創(chuàng)建的節(jié)點(diǎn)插入某個文檔的節(jié)點(diǎn)樹的最簡單的辦法是,讓它成為這個文檔某個現(xiàn)在節(jié)點(diǎn)的一個子節(jié)點(diǎn)。
- 創(chuàng)建一個新的元素 document.createElement(nodeName)
- 把這個新元素插入節(jié)點(diǎn)樹 parent.appendChild(child)
var testdiv=document.getElementById("testdiv");
var para=document.createElement("p");
testdiv.appendChild(para);
createTextNode方法
你現(xiàn)在已經(jīng)創(chuàng)建出了一個元素節(jié)點(diǎn)并把它插入了文檔的節(jié)點(diǎn)樹,這個節(jié)點(diǎn)是一個空白的p元素。現(xiàn)在把一些文本放入這個p元素,創(chuàng)建文本節(jié)點(diǎn),使用createTextNode方法。
createTextNode語法與createElement很相似。document.createTextNode(text)
在p元素中插入文本節(jié)點(diǎn)。
var txt=document.createTextNode("Hello World");
para.appendChild(txt);
綜合上面的過程,完整代碼如下:
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);
}
一個更復(fù)雜的組合
假如插入的內(nèi)容如下所示。
<p>This is <em>my</em>content.</p>
PS:自己寫完成,再與課本對對看有什么不一樣。
var para=document.createElement("p");
var testdiv=document.getElementById("testdiv");
var txt=document.createTextNode("This is ");
var em=document.createElement("em");
var myTxt=document.createTextNode("my");
var contentTxt=document.createTextNode("content.");
para.appendChild(txt);
para.appendChild(em);
em.appendChild(myTxt);
para.appendChild(contentTxt);
testdiv.appendChild(para);
重回圖片庫
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Image Gallery</title>
<link rel="stylesheet" type="text/css" href="picture.css" media="screen">
</head>
<script type="text/javascript" src="scripts/showpicture.js"></script>
<script type="text/javascript">
</script>
<body>
<h1>Snapshots</h1>
<ul id="imagegallery">
<li><a href="images/fireworks.jpg" title="A fireworks display">Fireworks</a></li>
<li><a href="images/coffee.jpg" title="A cup of black coffee">Coffee</a></li>
<li><a href="images/rose.jpg" title="A red,red rose" >Rose</a></li>
<li><a href="images/bigben.jpg" title="The famous clock">Big Ben</a></li>
</ul>
<!--下面這段代碼僅僅為showPic腳本服務(wù),如果能把結(jié)構(gòu)和行為徹底分開那最好不過了-->

<p id="description">Choose an image</p>
<!------------->
</body>
</html>
PS: 既然上面標(biāo)注的圖片和p只是為了讓DOM方法處理它們,那么讓DOM方法來創(chuàng)建它們才是最合適的選擇。我們需要做的就是1)將這些元素從文檔里刪掉。2)動態(tài)的將這些元素創(chuàng)建出來。3)把新創(chuàng)建的文檔元素插入到文檔中。
涉及到的方法有,創(chuàng)建元素節(jié)點(diǎn)createElement,設(shè)置屬性setAttribute,創(chuàng)建文本節(jié)點(diǎn)createTextNode。
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/placeholder.gif");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desText=document.createTextNode("Choose an image");
document.getElementsByTagName("body")[0].appendChild(placeholder);
document.getElementsByTagName("body")[0].appendChild(description);
-------------------------------------------HTML-DOM屬性
document.body.appendChild(placeholder);
document.body.appendChild(description);
PS:這段代碼有個問題,假如在body里面還會動態(tài)元素的話,它就會處于文檔的中間而不會一直保持在最后的位置。
在已有元素前插入一個新元素
DOM提供了名為insertBefore()方法,這個方法將把一個新元素插入到一個現(xiàn)有元素的前面。調(diào)用此方法你必須告訴它三件事情。
- 新元素:你想插入的元素(newElement)
- 目標(biāo)元素:你想把這個新元素插入到哪個元素(targetElement)之前
- 父元素:目標(biāo)元素的父元素(parentElement)
語法是:parentElement.insertBefore(newElement,targetElement)
我們不必搞清楚父元素到底是哪個,因?yàn)閠argetElement元素的parentNode就是它。既targetElement.parentNode=parentElement
比如說將圖片插入到清單之前,可以使用以下語句。
var gallery=document.getElementById("imagegallery");
gallery.parentNode.insertBefore(placeholder,gallery);
但是,現(xiàn)在我們需要將圖片插入到清單的后面,用下面的方法。
在現(xiàn)有方法后插入一個新元素
PS:我想當(dāng)然的認(rèn)為,既然有在insertBefore方法,那肯定也有insertAfter方法,可是沒有。但是我們可以自己實(shí)現(xiàn)。
function insertAfter(newElement,targetElement){
var parent=targetElement.parentNode;
if(parent.lastChild==targetElement){//目標(biāo)元素是parent的最后一個元素,直接追加到parent上。
parent.appendChild(newElement);
}else{//把新元素插入到目標(biāo)元素和目標(biāo)元素的下一個兄弟元素之間
parent.insertBefore(newElement,targetElement.nextSibling);
}
}
目標(biāo)元素的下一個兄弟元素既目標(biāo)元素targetElement.nextSibling
現(xiàn)在我們使用insertAfter函數(shù),使用該方法改寫最后兩句話。
var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
整個代碼改寫如下,需要注意的是,我需要增加一些判斷,判斷瀏覽器是否支持我使用的屬性,能保證平穩(wěn)的退化:
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.gif");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var desText=document.createTextNode("Choose an image");
var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
insertAfter(placeholder,placeholder);
}
圖片庫二次改進(jìn)版
- addLoadEvent函數(shù)
- insertAfter函數(shù)
- preparePlaceholder函數(shù)
- prepareGallery函數(shù)
- showPic函數(shù)
function showPic(whichpic) {
if(!document.getElementById("placeholder"))return false;
var source=whichpic.getAttribute("href");
var placeholder=document.getElementById("placeholder");
placeholder.setAttribute("src",source);
if(document.getElementById("description")){
var text=whichpic.getAttribute("title");
var description=document.getElementById("description");
description.firstChild.nodeValue=text;
}
return true;
}
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);
}
}
}
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.gif");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribute("id","description");
var descText=document.createTextNode("Choose an image");
description.appendChild(descText);
var gallery=document.getElementById("imagegallery");
insertAfter(placeholder,gallery);
insertAfter(description,placeholder);
}
addLoadEvent(preparePlaceholder);
addLoadEvent(prepareGallery);
Ajax
上面的例子,將某些節(jié)點(diǎn)放到JS中動態(tài)的添加。假如頁面只更新了一小部分,那怎樣才能真正得到原來并不存在于初始頁面中的內(nèi)容呢?
使用Ajax就可以做到只更新頁面中的一小部分內(nèi)容。它的主要優(yōu)勢就是對頁面的請求以異步方式發(fā)送到服務(wù)器。Ajax技術(shù)的核心就是XMLHttpRequest對象,這個對象充當(dāng)著瀏覽器中的腳本(客戶端)與服務(wù)器間的中間人的角色。以往的請求都由瀏覽器發(fā)出,而JS通過這個對象可以自己發(fā)送請求,同時也自己處理響應(yīng)。
雖然XMLHttpRequest得到了幾乎所有現(xiàn)代瀏覽器的支持。但問題是,不同瀏覽器實(shí)現(xiàn)XMLHttpRequest對象的方式不太一樣。為了保證跨瀏覽器,你不得不為同樣的事情寫不同的代碼分支。
不同的IE版本中使用的XMLHTTP對象也不完全相同。為了兼容所有的瀏覽器,getHTTPObject函數(shù)要這樣寫:
function getHTTPObject(){
if(typeof XMLHttpRequest == "undefined")
XMLHttpRequest=function(){
try{
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
}catch(e){}
try{
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
}catch(e){}
try{
return new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){}
return false;
}
return new XMLHttpRequest();
}
當(dāng)你腳本要使用XMLHttpRequest對象時,可以將這個新對象直接賦值給一個變量。var request=getHttpObject();
在這之前,我需要搞懂兩點(diǎn)。
- typeof的用法:可以使用 typeof 操作符來檢測變量的數(shù)據(jù)類型。typeof 一個沒有值的變量會返回 undefined。
- XMLHttpRequest=function(){}是什么用法?相當(dāng)于檢測XMLHttpRequest對象是否存在。
XMLHttpRequest有很多方法,最有用的是open方法,用來指定服務(wù)器上將要訪問的文件,指定請求類型:GET、POST或SEND。
function getNewContent(){
var request=getHTTPObject():
if(request){
//發(fā)送GET請求,請求與ajax.html文件位于同一目錄的example.txt文件
request.open("GET","example.txt",true);
//onreadystatechange是一個事件處理函數(shù),服務(wù)器響應(yīng)時被觸發(fā)
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 doesn\'t support XMLHttpRequest");
}
}
addLoadEvent(getNewContent);
服務(wù)器在向XMLHttpRequest對象發(fā)回響應(yīng)時,該對象有許多屬性可用,readyState有5個可能的值。
- 0表示未初始化
- 1表示正在加載
- 2表示加載完畢
- 3 表示正在交互
- 4表示完成 // 可以直接使用服務(wù)器發(fā)送回來的數(shù)據(jù)。
可以通過responseText和responseXML屬性來訪問發(fā)送回來的數(shù)據(jù)。
- responseText用于保存文本字符串形式的數(shù)據(jù)
- responseXML用于保存Content-Type頭部中指定“text/xml”的數(shù)據(jù),其實(shí)是一個DocumentFragment對象,可以使用DOM來處理這個對象。
重做下例子,在谷歌運(yùn)行發(fā)出現(xiàn)以下錯誤,safari就可以:

PS:原因是有些瀏覽器限制Ajax的使用協(xié)議,在Chrome中,如果你使用了file://協(xié)議從自己的硬盤中加載example.txt,就會看到Cross origin requests are only supported for protocol schemes的錯誤。
注意一點(diǎn):異步請求有個容易忽略的點(diǎn)就是腳本在發(fā)送XMLHttpRequest請求后,仍然會繼續(xù)執(zhí)行,不會等待響應(yīng)返回。
Ajax的特色就是減少重復(fù)加載頁面的次數(shù),但是缺少狀態(tài)記錄的技術(shù)會與瀏覽器的一些使用慣例產(chǎn)生沖突,導(dǎo)致用戶無法使用后退按鈕或者無法為特定狀態(tài)下的頁面添加書簽。
漸進(jìn)增強(qiáng)與Ajax
構(gòu)建Ajax網(wǎng)站,從一開始就從構(gòu)建一個常規(guī)網(wǎng)站開始,然后再使用Ajax。
Hijax
Hijax指漸進(jìn)增強(qiáng)的使用Ajax。
Ajax應(yīng)用主要依賴后臺服務(wù)器,實(shí)際上是服務(wù)器端的腳本語言完成了絕大部分工作。XMLHttpRequest對象作為瀏覽器與服務(wù)器之間的“中間人”,它只是負(fù)責(zé)傳遞請求和響應(yīng)。如果把這個中間人請開。瀏覽器與服務(wù)器之間的請求和響應(yīng)應(yīng)該繼續(xù)完成。
構(gòu)建表單的傳統(tǒng)方式。
- 讓表單把整個頁面都提交到服務(wù)器,然后服務(wù)器再發(fā)回來包含反饋的新頁面。所有處理操作都在服務(wù)器上完成,用戶在表單中輸入的數(shù)據(jù)則由服務(wù)器負(fù)責(zé)取得并保存在數(shù)據(jù)庫里的數(shù)據(jù)進(jìn)行比較,看是不是真的存在這個用戶。
Hijax的方式
- 攔截提交表單的請求,讓XMLHttpRequest請求來代為發(fā)送。提交表單觸發(fā)的是submit事件,因此只要通過onsubmit事件處理函數(shù)捕獲該事件,就可以取消它的默認(rèn)操作,然后代之以一個新的操作:通過XMLHttpRequest對象向服務(wù)器發(fā)送數(shù)據(jù)。
Ajax應(yīng)用主要依賴于服務(wù)器端處理,而非客戶端處理。
具體的Hijax實(shí)例參考筆記9: