1. 程式人生 > >《JavaScript DOM程式設計藝術》筆記:動態建立標記(五)

《JavaScript DOM程式設計藝術》筆記:動態建立標記(五)

此前見過的絕大多數DOM方法只能用來查詢元素。getElementById和getElementsByTagName都可以方便快捷地找到文件中的某個或者特定的元素節點,這些元素隨後可以用諸如setAttribute(改變某個屬性的值)和nodeValue(改變某個元素節點所包含的文字)之類的方法和屬性來處理。我們的圖片庫就是這樣實現的。showPic函式先找出id屬性值是placeholder和description的兩個元素,然後重新整理他們的內容。placeholder元素的src屬性是用setAttribute修改的,description元素所包含的文字是用nodeValue屬性修改的。這兩種情況裡,都是對已經存在的元素做出修改。

這是絕大多數js函式的工作原理。網頁的結構由標記負責建立,js函式只用來改某些細節而不改變其底層結構。js也可以用來改變網頁的結構和內容。

  • 一些傳統方法

在學習DOM方法在web瀏覽器中往文件新增標記時,先回顧兩個過去使用的技術,即document.write和innerHTML

  • 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>

document.write的最大缺點是它違背了行為應該與表現分離的原則。即使把document.write語句挪到外部函式裡,也還需要在標記body部分使用<script>標籤才能呼叫那個函式。

下面這個函式以一個字串為引數,它將把一個<p>標籤、字串和一個</p>標籤拼接在一起。拼接後的字串被儲存到變數str,然後用document.write()方法寫出來:

function insertParagraph(text){
	var str = "<p>";
	str += text;
	str += "</p>";
	document.write(str);
}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8"/>
	<title>Test</title>
</head>
<body>
	<script src="example.js"></script>
	<script>
		insertParagraph("This is inserted.");
	</script>
</body>
</html>

像上面這樣把js和HTML程式碼混合在一起是一種很不好的做法。這樣的標記既不容易閱讀和編輯,也無法享受把行為與結構分離開來的好處。

這樣的文件很容易導致驗證錯誤。比如在第一個例子裡<script>標籤後面的<p>很容易被誤認為是<p>標籤,而在<script>標籤的後面開啟<p>標籤是非法的。事實上,那個<p>和</p>只不過是一個將被插入文件的字串的組成部分而已。

還有.MIME型別application/xhtml+xml與document.write不相容,瀏覽器在呈現這種xhtml文件時根本不會執行document.write方法.

從某種意義上講,使用document.write方法有點像使用<font>標籤去設定字型和顏色。雖然這兩種技術在HTML文件裡都工作的不錯,但它們都不夠優雅。

把結構、行為和樣式分開永遠都是一個好主意。只要有可能,就應該用外部css檔案代替<font>標籤去設定和管理網頁的樣式資訊,最好用外部js去控制網頁的行為。應該避免在<body>部分亂用<script>標籤,避免使用document.write方法。

  • innerHTML屬性

現如今的瀏覽器幾乎都支援屬性innerHTML,這個屬性並不是W3CDOM標準的組成部分,但現已經包含到HTML5規範中。它始見於ie4瀏覽器,並從那時逐漸被其他的瀏覽器接受。

innerHTML屬性可以用來讀寫某給定元素裡的HTML內容。要了解塔如何工作,請把下面的程式碼插入test.html

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

div元素的id是testdiv。它包含一個元素節點(p元素)。這個p元素又有一些子節點。其中有兩個文字節點,值分別是This is 和content。還有一個元素節點(em元素),em元素本身包含一個文字節點,這個文字及節點的值是my。

window.onload = function(){
	var testdiv = document.getElementById("testdiv");
	alert(testdiv.innerHTML);
}

innerHTML屬性無細節可言。要想獲得細節,就必須使用DOM方法和屬性。標準化的DOM像手術刀一樣精細,innerHTML屬性就像一把大錘那樣粗放。

在你需要把大段的HTML內容插入網頁時,innerHTML屬性更適用。它既支援讀取,又支援寫入,你不僅可以用它來讀出元素的HTML被容,還可以用它把HTML內容寫入元素。

編輯test.html檔案,讓id屬性值等於testdiv的元素變成空白:

	<div id="testdiv">
		
	</div>
window.onload = function(){
	var testdiv = document.getElementById("testdiv");
	testdiv.innerHTML = "<p>This id<em>my</em> content.</p>";
}

利用這個技術無法區分"插入一段HTML內容"和“替換一段HTML內容"。testdiv元素裡沒有HTML內容無關緊要:一旦你使用了innerHTML屬性,它的全部內容都將被替換。

在test.html檔案裡,把id屬性值等於testdiv的元素的內容修改回它原來的樣子

example.js檔案保持不變,如果你在web瀏覽器裡重新整理test.html檔案,結果將和剛才一樣。包含在testdiv元素裡HTML內容被innerHTML屬性完全改變了,原來的HTML內容未留下任何痕跡。

在需要把一大段HTML內容插入一份文件時,innerHTML屬性可以讓你又快又簡單地完成這一任務。不過,innerHTML屬性不會返回任何對剛插入的內容的引用。如果想對剛插入的內容進行處理,則需要使用DOM提供的那些精確的方法和屬性。

innerHTML屬性要比document.write()方法更值得推薦。使用innerHTML屬性,你就可以把js程式碼從標記中分離出來。用不著再在標記的<body>部分插入<script>標籤。

類似於document.write()方法,innerHTML屬性也是HTML專有屬性,不能用於任何其他標記語言文件。瀏覽器在呈現正宗的XHTML文件時會直接忽略掉innerHTML屬性。

在任何時候,標準的DOM都可以用來代替innerHTML。雖說這往往需要多編寫一些程式碼才能獲得同樣的效果,但DOM同時也提供了更高的精確性和更請打的功能性。

  • DOM方法

getElementById和getElementByTagName等方法可以把關於文件結構和內容的資訊檢索出來,它們非常有用。

DOM是文件的表示。DOM所包含的資訊與文件裡的資訊一一對應。你只要會用正確的方法,就可以獲取DOM節點樹上任何一個節點的細節。

DOM是一條雙向車道。不僅可以獲取文件的內容,還可以更新文件的內容。如果你改變了DOM節點樹。文件瀏覽器裡呈現的效果就會發生變化。你已經見識過setAttribute方法的神奇之處了。用這個方法可以改變DOM節點樹上的某個屬性節點,相關文件在瀏覽器裡呈現就會發生相應的變化。不過setAttribute方法並未改變文件的物理內容,如果用文字編輯器而不是瀏覽器去開啟這個文件,我們將看不到任何變化。只有在瀏覽器開啟那份文件時才能看到文件呈現效果的變化。這是因為瀏覽器實際顯示的是那顆DOM節點樹。在瀏覽器看來,DOM節點樹才是文件。

一旦明白了這個道理,以動態方式實時建立標記就不那麼難以理解了。並不是在建立標記,而是在改變DOM節點樹。做到這一點是一定要從DOM的角度去思考。

在DOM看來,一個文件就是一棵節點樹。如果你想在節點樹上新增內容,就必須插入新的節點。如果你想新增一些標記文件,就必須插入元素節點。

  • creatElement方法

編輯test.html檔案,讓id等於testdiv的那個<div>標籤的內容變成空白

我想把一段文字插入testdiv元素。用DOM的語言來說,就是想新增一個p元素節點,並把這個節點作為div元素節點的一個子節點。(div元素節點已經有了一個子節點,那是一個id屬性節點,值是testdiv)

這項任務需要分兩個步驟:

(1)建立一個新的元素;

(2)把這個新元素插入節點樹。

第一個步驟要用DOM方法createElement來完成。

document.creatElement(nodeName)

下面這條語句將建立一個p元素

document.creatElement("p");

這個方法本身並不能影響頁面表現,還需要把這個新創建出來的元素插入到文件中去。為此你需要有個東西來引用這個新創建出來的節點。不論何時何地,只要你使用了creatElement方法,就應該把創建出來的元素賦給一個變數就總是個好主意:

var para = document.creatElement("p");

變數para現在包含在一耳光指向剛創建出來的那個p元素的引用。現在,誰然這個新創建出來的p元素已經存在了,但它還不是任何一顆DOM節點樹的組成部分,這種情況稱為文件碎片,還無法顯示在瀏覽器的視窗畫面裡。不過,他已經像任何其他的節點那樣有了自己的DOM屬性。

這個無家可歸的p元素現在已經有一個nodeType和一個nodeName值。

window.onload = function(){
	var para = document.createElement("p");
	var info = "nodeName:";
	info += para.nodeName;
	info += "nodeType:"
	info += para.nodeType;
	alert(info);
}

新節點已經存在,它有一個取值為p的nodeName屬性。它還有一個取值為1的nodeType屬性,而這意味著它是一個元素節點,不過,這個節點現在還未被連結到test.html文件的節點樹上。

  • appendChild方法

把建立的節點插入某個文件的節點樹的最簡單方法是,讓它成為這個文件某個現有節點的一個子節點。

具體到這個例子,是要把一段新文字插入到test.html文件中id是testdiv的元素節點。換句話說,我想讓創新的p元素成為testdiv元素的一個子節點。你可以用appendChild方法來完成這一任務。下面是appendChild方法的語句:

parent.appendChild(child)

具體到test.html文件這個例子,上面這個語法中的child就是剛才用creatElement方法創建出來的,parent就是id是testdiv的元素節點。我需要用一個DOM方法得到“testdiv"節點,最簡單的方法是使用getElementById方法。

像往常一樣,你把這個元素賦給一個變數,這可以讓你的程式碼簡明易讀:

var testdiv = document.getElementById("testdiv");

變數testdiv現在包含著一個指向那個id等於testdiv的元素的引用。

像下面這樣用addpendChild方法把變數para插入變數testdiv:

testdiv.appendChild(para);

新建立的p元素現在成為了testdiv元素的一個子節點。它不再是js世界裡的孤兒,已經被插入到test.html文件的節點樹裡了。

在使用appendChild方法時,不必非得使用一些變數來引用父節點和子節點。事實上完全可以把上面那些語句寫成

document.getElementById("testdiv").appendChild(document.createElement("p"));
  • createTextNode方法

你現在已經創建出了一個元素節點並把它插入了文件的節點樹,這個節點是一個空白的p元素。你想把一些文字放入這個p元素,但createElement方法幫不上忙,它只能建立元素節點。你需要建立一個文字節點,你可以用createTestNode方法來實現它。

creatTextNode的語法與createElement很相似:

document.creatTextNode(text)

下面這條語句將創建出一個內容為helloworld的文字節點:

document.createTextNode("Hello World");

和剛才一樣,把這個新建立的節點也賦值給一個變數:

var txt = document.creatTextNode("Hello world");

變數txt現在包含指向新建立的那個文字節點的引用。這個節點現在也是js世界裡的一個孤兒,因為它還未被插入任何一個文件的節點樹。

可以用appendChild方法把這個文字節點插入為某個現有元素的子節點。我將把這個文字節點插入到我在上一小節建立的p元素。因為在上一小節裡我們已經把那個p元素存入了變數para,現在又把新闖將的文字節點存入了變數txt,所以現在可以用下面這條語句來達到我的目的:

para.appendChild(txt);

內容為“HelloWorld" 的文字節點就成為那個p元素的一個節點了。

現在,試著把下面這段程式碼寫入example.js檔案:

window.onload = function(){
	// var testdiv = document.getElementById("testdiv");
	// testdiv.innerHTML = "<p>This id<em>my</em> content.</p>";
	var para = document.createElement("p");
	var testdiv = document.getElementById("testdiv");
	testdiv.appendChild(para);
	var txt = document.createTextNode("Hello World");
	para.appendChild(txt);
	//document.getElementById("testdiv").appendChild(document.createElement("p"));

}

這個例子是按照以下順序來建立和插入節點的:

  1. 建立一個p元素節點。
  2. 把這個p元素節點追加到test.html文件的一個元素節點上。
  3. 建立一個文字節點。
  4. 把這個文字節點追加到剛才建立的那個p元素節點上。

appendChild方法還可以用來連線那些尚未成為文件樹一部分的節點。也就是說,以下步驟順序同樣可以達到目的。

  1. 建立一個p元素節點。
  2. 建立一個文字節點。
  3. 把這個文字節點追加到第1步建立的p元素節點上。
  4. 把這個p元素節點追加到test.html文件中的一個元素節點上。
	var para = document.createElement("p");
	var txt = document.createTextNode("Hello World");
	para.appendChild(txt);
	var testdiv = document.getElementById("testdiv");
	testdiv.appendChild(para);
  • 一個更復雜的組合

剛才介紹innerHTML屬性時,我們使用瞭如下所示的HTML內容:

"<p>This id<em>my</em> content.</p>"

與建立一個包含著一些文字的p元素相比,這個步驟要複雜不少,為了把這些標記插入test.html文件,先把它轉換為一顆節點樹。

  1. 建立一個p元素節點並把它賦值給變數para。
  2. 建立一個文字節點並把它賦值給變數txt1.
  3. 把txt1追加到para上。
  4. 建立一個em元素節點並把它賦給變數emphasis。
  5. 建立一個文字節點並把它賦值給txt2.
  6. 把txt2追加到emphasis上。
  7. 把emphasis追加到para上。
  8. 建立一個文字節點把它賦值給變數txt3.
  9. 把txt3追加到para上。
  10. 把para追加到test.html文件中的testdiv元素上。
window.onload = function(){
	// var testdiv = document.getElementById("testdiv");
	// testdiv.innerHTML = "<p>This id<em>my</em> content.</p>";
	var para = document.createElement("p");
	var txt1 = document.createTextNode("This is ");
	para.appendChild(txt1);

	var emphasis = document.createElement("em");
	var txt2 = document.createTextNode("my");
	emphasis.appendChild(txt2);
	para.appendChild(emphasis);

	var txt3 = document.createTextNode(" content");	
	para.appendChild(txt3);

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

	//document.getElementById("testdiv").appendChild(document.createElement("p"));

}

  • 重回圖片庫

我們看一下顯示圖片的程式碼

<h1>Snapshots</h1>
 	<ul id="imageallery">
 	<li>
 		<a href="image/btn_acomlete_1.png"  title="A fireworks display">Firework</a>
 	</li>
 	 <li>
 		<a href="image/btn_acomlete_2.png"  title="A cup of tea">tea</a>
 	</li>
  	<li>
 		<a href="image/btn_allcomlete_1.png" title="A red rose">Rose</a>
 	</li>
 	<li>
 		<a href="image/btn_allcomlete_2.png" title="A lalala">Big bar</a>
 	</li> 
 	<img id="placeholder" src="image/img_receive.png" alt="my image gallery" />	
 	<p id="description">Choose an image.</p>
 	</ul>	

程式碼中有一個圖片顯示和一段文字僅僅是showPic指令碼服務的。若能把結構和行為徹底分開最好不過。既然這些元素的存在只是為了讓DOM方法處理它們,那麼用DOM方法來建立它們才是最合適的選擇。

先編寫一個函式preparePlaceholder,在文件載入時呼叫這個函式。

  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","image/img_receive.png");
         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);

最後一步是把建立的元素插入文件。很湊巧,因為圖片清單(<ul>....</ul>)剛好是文件中的最後一個元素,所以如果把placeHolder和description元素追加到body元素節點上,它們就會出現在圖片清單的後面。我們可以通過標籤名“body”引用body標籤(作為第一個也是唯一一個body元素的引用)。

    document.getElementsByTagName("body")[0].appendChild(placeholder);
         document.getElementsByTagName("body")[0].appendChild(description);

當然也可以使用HTML-DOM提供的body屬性

         // document.body.appendChild(placeholder);
         // document.body,appendChild(description);
  • 在已有元素前插入一個新元素

DOM提供了名為inserBefore()方法,這個方法將把一個新元素插入到一個現有元素的前面。在呼叫此方法時,你必須告訴它三件事。

  1. 新元素:你想插入的元素(newElement)。
  2. 目標元素:你想把這個新元素插入到哪個元素(targetElement)之前。
  3. 父元素:目標元素的父元素(parentElement)。

下面是這個方法的呼叫語法:

parentElement.insertBefore(newElement,targetElement)

我們不必搞清楚父元素到底是哪個,因為targetElement元素的parentNode屬性值就是它。在DOM裡,元素節點的父元素必須是另一個元素節點(屬性節點和文字節點的子元素不允許時元素節點)。

比如說,下面這條語句可以把placeholder和description元素插入到圖片清單的前面。

var gallery = document.getElementById("Imagegallery");

gallery.parentNode.insertBefore(placeholder,gallery);

此時,變數gallery的parentNode屬性值是body元素,所以placeholder元素將被插入為body元素的新子元素,它被插入到他的兄弟元素gallery的前面。

還可以吧description元素也插入到gallery元素之前,成為它的一個兄弟元素:

gallery.parentNode.insertBefore(description,gallery);

在gallery清單的前面插入placeholder和description文字段的效果如圖

      function preparePlaceholder(){

         var placeholder = document.createElement("img");
         placeholder.setAttribute("id","placeholder");
         placeholder.setAttribute("src","image/img_receive.png");
         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);

         document.getElementsByTagName("body")[0].appendChild(placeholder);
         document.getElementsByTagName("body")[0].appendChild(description);
         var gallery = document.getElementById("imageallery");

         gallery.parentNode.insertBefore(placeholder,gallery);
         gallery.parentNode.insertBefore(description,gallery);
         // document.body.appendChild(placeholder);
         // document.body,appendChild(description);
      }
      //window.onload = prepareGallery;
      addLoadEvent(prepareGallery);
      addLoadEvent(preparePlaceholder);

  • 在現有方法後插入一個新元素

1.編寫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);
         }
      }

這個函式用到了以下DOM方法和屬性:

parentNode屬性

lastChild屬性

appendChild方法

insertBefore方法

nextSibling屬性

首先這個函式有兩個引數:一個是將被插入的新元素,另一個是目標元素。這兩個引數通過變數newElement和targetElement被傳遞到這個函式:

function insertAfter(newElement,targetElement)

把目標元素的parentNode屬性值儲存到變數parent裡

var parent = targetElement.parentNode;

接下來,檢查目標元素是不是parent的最後一個子元素,即比較parent元素的lastChild屬性值與目標元素是否存在等於關係。

if(parent.lastChild == targetElement)

如果是,就用appendChild方法把新元素追加到parent元素上,這樣新元素就恰好被插入到目標元素之後:

parent.appendChild(newElement);

如果不是,就把新元素插入到目標元素和目標元素的下一個兄弟元素之間。目標元素的下一個兄弟元素即目標元素的nextSibling屬性。用insertBefore方法把新元素插入到目標元素的下一個兄弟元素之前:

parent.insertBefore(newElement,targetElement.nextSibling);
  • 使用insertAfter函式

我們將insertAfter函式用在我的preparePlaceholder函式中。首先,得到圖片清單:

         var gallery = document.getElementById("imageallery");
         insertAfter(placeholder,gallery);

我們在新增的語句中使用了一些新的DOM方法,但沒有測試瀏覽器是否支援它們。為了確保這個函式有足夠的退路,還需要再增加幾條語句:

         if(!document.createElement) return false;
         if(!document.createTextNodet) return false;
  • 圖片庫二次改進版

在js檔案裡包含5個不同的函式

addLoadEvent和insertAfter屬於通用型函式,它們在許多場合都能派上用場。

preparePlaceholder函式負責建立一個img元素和一個p元素。這個函式將把這些新建立的元素插入到節點樹裡圖片庫清單的後面。prepareGallery函式負責處理事件。這個函式將遍歷處理圖片庫清單裡的沒個連結。當用戶點選這些連結中的某一個時,就會呼叫showPic函式。

showPic函式負責把佔位符圖片切換為目標圖片。

為了啟用這些功能,用addLoadEvent函式呼叫preparePlaceholder和prepareGallery函式。

	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("placeholder"))
         {
            if(whichpic.getAttribute("title")){
               var text = whichpic.getAttribute("title");//獲取whichpic物件的title屬性並把值存入text
            }
            else
            {
               var text = " ";
            }
            var description = document.getElementById("description");
            if(description.firstChild.nodeType == 3)
            {
               description.firstChild.nodeValue = text;
            }


         }
         return true;
   		
         //alert(description.fristChild.nodeValue);
   	}
      function prepareGallery(){
         if(!document.createElement) return false;
         if(!document.createTextNodet) return false;

         if(!document.getElementsByTagName) return false;
         if(!document.getElementById) return false;
         if(!document.getElementById("imageallery")) return false;
         var gallery = document.getElementById("imageallery");
         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 preparePlaceholder(){

         var placeholder = document.createElement("img");
         placeholder.setAttribute("id","placeholder");
         placeholder.setAttribute("src","image/img_receive.png");
         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);

         document.getElementsByTagName("body")[0].appendChild(placeholder);
         document.getElementsByTagName("body")[0].appendChild(description);
         var gallery = document.getElementById("imageallery");
         insertAfter(placeholder,gallery);

         // gallery.parentNode.insertBefore(placeholder,gallery);
         // gallery.parentNode.insertBefore(description,gallery);
         // document.body.appendChild(placeholder);
         // document.body,appendChild(description);
      }


      function insertAfter(newElement,targetElement){
         var parent = targetElement.parentNode;
         if(parent.lastChild == targetElement){
            parent.appendChild(newElement);
         }
         else
         {
            parent.insertBefore(newElement,targetElement.nextSibling);
         }
      }
      //window.onload = prepareGallery;
      addLoadEvent(prepareGallery);
      addLoadEvent(preparePlaceholder);
  • Ajax

Ajax用於概括非同步載入頁面內容的技術。Ajax的主要優勢就是對頁面的請求以非同步方式傳送到伺服器。而伺服器不會用整個頁面來響應請求,他會在後臺處理請求,於此同時使用者還能繼續瀏覽頁面並與頁面互動。你的指令碼則可以按需載入和建立頁面內容,而不會打斷使用者的瀏覽體驗。利用Ajax,web應用可以呈現出功能豐富、互動敏捷、類似桌面應用般的體驗,就像你使用谷歌地圖時的感覺一樣。

  • XMLHttpRequest物件

Ajax技術核心就是XMLHttpRequest物件。這個物件充當著瀏覽器中的指令碼(客戶端)與伺服器之間的中間人的角色。以往的請求都由瀏覽器發出,而js通過這個物件可以自己傳送請求,同時也自己處理響應。

<!DOCTYPE html>
<html lang = "en">
 <head>
 <meta charset = "utf-8"/>
 <title>Ajax</title>
 <!-- <link rel="stylesheet" href="layout.css" media="screen"/> -->
 </head>
 <body>
 	<div id = "new"></div>
 	<script src="script/addLoadEvent.js"></script>
    <script src="script/getHTTPObject.js"></script>
    <script src="script/getNewContent.js"></script>
   <!-- <script src="example.js"></script> -->
 </body>
</html>

新建一個檔案example.txt,輸入This was loaded asymchronously!

這個檔案充當伺服器端指令碼的輸出。多數情況下,伺服器端指令碼在接到請求後,還會做一番處理再輸出結果。

為了相容所有瀏覽器,getHTTPObject.js檔案中的getHTTPObject函式這樣來寫:

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();

	}
}

getHTTPObject通過物件檢測了XMLHttpRequest。如果失敗,則繼續檢測其他方法,最終返回false或一個新的XMLHttpResquest物件。

這樣,在你的指令碼中要使用XMLHttpRequest物件時,可以將這個物件直接賦值給一個變數:

var request = getHTTPObject();

XMLHttpRequest 物件有許多的方法。其中最有用的是open方法,它用來指定伺服器上將要訪問的檔案,指定請求型別:GET 、POST或SEND。這個方法的第三個引數用於指定請求是否以非同步方式傳送和處理。

在getNewContent.js檔案中新增下列程式碼:

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 doesn\'t support XMLHttpRequest");
	}
}
addLoadEvent(getNewContent);

當頁面載入完成後,以上程式碼會發起一個GET請求,請求ajax.html檔案位於同一目錄的example.txt檔案。

request.open("GET","example.txt",true);

程式碼中的onreadystatechange是一個事件處理函式,它會在伺服器給XMLHttpRequest物件送回響響應的時候被觸發執行。在這個處理函式中,可以根據伺服器的具體響應做相應的處理。

在此,我們給它指定了一個處理函式:

request.onreadystatechange = function(){//處理響應}

當然,也可以引用一個函式。下面的程式碼就會在onreadystatechange被觸發時執行名為dosomething的函式:

注:在為onreadystatechange指定函式引用時,不要在函式名後面加括號。因為加括號表示立即呼叫函式,而我們在此只想把函式自身的引用(而不是函式結果)賦值給onreadystatechange屬性。

request.onreadystatechange = doSomething;

在指定了請求的目標,也明確瞭如何處理響應之後,就可以用send方法來發送請求了:

request.send(null);

如果瀏覽器不支援XMLHttpRequest物件發回響應時,該物件有許多屬性可用,瀏覽器會在不同階段更新readyState屬性的值:

  • 0表示未初始化
  • 1表示正在載入
  • 2表示載入完成
  • 3表示正在互動
  • 4表示完成

只要readyState屬性的值變成了4,就可以訪問伺服器傳送回來的資料了。

訪問伺服器傳送回來的資料要通過兩個屬性完成。一個是responseText屬性,這個屬性用於儲存文字字串形式的資料。另一個屬性是responseXML屬性,用於儲存Content-type頭部中指定為“text/xml”的資料,其實是一個DocumentFragment物件。你可以用各種DOM方法來處理這個物件。而這也是XMLHttpRequest這個名稱裡有XML的原因。

在這個例子中,onreadystatechange事件處理函式在等到readyState值變成4之後,就會從responseText屬性裡取得文字資料,然後把資料放到一個段落中,再將新段落新增到DOM裡:

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);
			}
		};

//TODO:我並沒有載入成功***********************************