[筆記8]JavaScript DOM編程藝術(shù)_動態(tài)創(chuàng)建標(biāo)記

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>
屏幕快照 2017-02-03 下午11.13.04.png

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>
屏幕快照 2017-02-04 下午10.57.46.png

從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)和行為徹底分開那最好不過了-->
![](images/placeholder.gif)
<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就可以:

屏幕快照 2017-02-07 下午10.39.12.png

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:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容