DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> JavaScript學習總結【9】、DOM Ready
JavaScript學習總結【9】、DOM Ready
編輯:JavaScript基礎知識     

1、DOM

  DOM(Document Object Model)即文檔對象模型,是從文檔中抽象出來的,DOM 操作的對象就是文檔,DOM 將 HTML 文檔呈現為帶有元素、屬性和文本的樹結構,即節點樹。通過 DOM,JS 可創建動態的 HTML,可以使網頁顯示動態效果並實現與用戶的交互功能。DOM 給我們提供了用程序來動態控制 HTML 的接口(也叫API),因此 DOM 處在 JS 賦予 HTML 具備動態交互和效果能力的核心地位上。想要安全的操作 DOM,必須等到頁面中所有的 HTML 都解析成 DOM 節點,才能進行操作,因此我們必須了解 DOMReady。在這之前我們先來回顧一下 DOM節點。

  (1)、常見的節點類型

  常見的節點類型有以下 7 種:

節點類型 說明 數值常量 Element(元素節點) HTML標簽元素。 1 Attr(屬性節點) 元素節點的屬性。 2 Text(文本節點) 元素節點或屬性節點中的文本內容。 3 Comment(注釋節點) 表示注釋的內容。 8 Document(文檔節點) 表示整個文檔(DOM 樹的根節點,即 document )。 9 DocumentType(文檔類型節點) <!DOCTYPE html>就是文檔類型節點。 10 DocumentFragment(文檔片段節點) 表示文檔的一部分或者是一段,它不屬於文檔樹。 11

  (2)、節點類型說明 

  元素節點,就是 HTML 標簽元素,如 <div> 、 <p>、<ul> 等。

  屬性節點,就是元素節點的屬性,如 id 、class 、name 等。屬性節點不能被看作是元素節點,因而在 DOM 中屬性沒有被認為是文檔樹的一部分,換句話就是說屬性節點是包含他的元素節點的一部分,他並不作為一個單獨的節點在文檔樹中出現。

  文本節點,就是只包含文本內容的節點。可以包含更多信息,也可以只包含空白,在文檔樹中元素的文本內容和屬性的文本內容都是由文本節點來表示的。

  注釋節點,就是文檔中的注釋,其形式為 <!-- 這是一個注釋 -->。

  文檔節點,就是整個文檔,是文檔樹的根節點,是文檔中所有其他節點的父節點。這裡需要注意:文檔節點並不是 HTML 文檔的根元素,在構造 DOM 樹時,根元素並不適合作為根節點,於是就有了文檔節點,而根元素是作為文檔節點的子節點出現的。將整個 HTML 文檔代碼之上看為一個文檔節點,這個節點下包含一個文檔類型節點 <!DOCTYPE html> 和一個元素節點 <html>,兩個子節點。

  文檔類型節點,每一個 Document 都有一個 DocumentType 屬性,<!DOCTYPE html> 就是文檔類型節點。

  文檔片段節點,是輕量級的或最小的 Document 對象,它表示文檔的一部分或者是一段,它不屬於文檔樹。不過它有一種特殊的行為,這一行為非常有用,比如當請求把一個DocumentFragment 節點插入到文檔的時候,插入的不是 DocumentFragment 自身,而是它所有的子孫節點。這時 DocumentFragment 就成了有用的占位符,暫時存放那些依次插入文檔的節點,同時它還有利於實現文檔的剪切、復制、粘貼等操作。像 JS 代碼中插入元素所定義的變量,這個變量只是作為一個臨時的占位符。這就是所謂的文檔片段節點。

  文檔片段也叫文檔碎片,創建一個文檔碎片可使用 document.createDocumentFragment() 方法,可直接給父節點下插入 n 個子節點。在理論上文檔碎片可以提高 DOM 操作性能。文檔碎片在低版本浏覽器可以大大提高頁面性能,但是在高級的浏覽器,幾乎性能沒有提高,而且還會影響性能。所以不建議使用,這東西基本是已經淘汰了。下面是創建文檔碎片的實例。

  實例:在 ul 元素下插入一個文檔碎片

 <body>
 <ul id="listNode"></ul>
 
 <script>
 //創建一個文檔碎片
 var frag = document.createDocumentFragment();
 //使用循環設置創建10個li元素
 for(var i = 0; i < 10; i++){
     //創建一個li元素
     var oLi = document.createElement('li');
     //li元素顯示的內容
     oLi.innerHTML = 'list ' + i;
     //創建完畢後插入文檔碎片中
     frag.appendChild(oLi);
 }
 //最後將文檔碎片插入到父節點ul元素下
 document.getElementById('listNode').appendChild(frag);
 </script>
 </body>

 

  (3)、節點類型、節點名稱和節點值

  屬性的一系列操作是與元素的類型息息相關的,如果我們不對元素的節點類型作判斷,就不知道如何操作:例如:obj.xx = yy 還是 obj.setAttribute(xx, yy),setAttribute() 方法可添加一個新屬性並指定值,或者把一個現有的屬性設定為指定的值,如果我們知道節點的類型,就可以直接設置或者是使用方法設置,所以我們有必要判斷節點的類型,避免耗費資源,造成意想不到的結果。判斷節點類型可使用 nodeType 屬性用數值常量進行判斷,其操作很簡單,就是判斷某個節點的節點類型是否等於該節點類型的數值常量。

  實例:節點類型判斷

 <body>
 <!-- 這是一個注釋。 -->
 <div id="div1">這是一個div元素節點。</div>
 
 <script>
 //判斷是否為注釋節點
 var commentNode = document.body.childNodes[1];
 if(commentNode.nodeType == 8){
     alert('該節點是注釋節點。');
 }
 
 //判斷是否為元素節點
 var divNode = document.getElementById('div1');
 if(divNode.nodeType == 1){
     alert('該節點是元素節點。');
 }
 
 //判斷是否為文本節點
 var textNode = document.getElementById('div1').childNodes[0];
 if(textNode.nodeType == 3){
     alert('該節點是文本節點。');
 }
 </script>

 

  其實我們在獲取節點的子節點時,不使用 childNodes 屬性,而使用 children 屬性,就可以避免這一問題,因為通過 childNodes 屬性返回的是子節點集合,不僅包括元素節點,而且還包括文本節點,浏覽器會將標簽之間的空白默認為文本節點,而使用 children 屬性,則只返回元素節點,不包括文本節點,還不包括注釋節點。

  節點名稱和節點值,直接使用實例演示:

 <body>
 <!-- nodeName 和 nodeValue 演示 -->
 <div id="div1">節點名稱和節點值。</div>
 <script>
 //元素節點 = nodeType:1
 var divNode = document.getElementById('div1');
 console.log('元素節點的節點名稱和值:' + divNode.nodeName + '/' + divNode.nodeValue);
 //nodeName:返回 元素的標簽名(DIV)全部大寫。nodeValue:返回 null
 
 //屬性節點 = nodeType:2
 var attrNode = divNode.attributes[0];
 console.log('屬性節點的節點名稱和值:' + attrNode.nodeName + '/' + attrNode.nodeValue);
 //nodeName:返回 屬性的名稱(id)。nodeValue:返回 屬性的值(div1)
 
 //文本節點 = nodeType:3
 var textNode = divNode.childNodes[0];
 console.log('文本節點的節點名稱和值:' + textNode.nodeName + '/' + textNode.nodeValue);
 //nodeName:返回 #text。nodeValue:返回 節點所包含的文本
 
 //(comment)注釋節點    = nodeType:8
 var commentNode = document.body.childNodes[1];
 console.log('注釋節點的節點名稱和值:' + commentNode.nodeName + '/' + commentNode.nodeValue);
 //nodeName:返回 #comment。nodeValue:返回 注釋的內容
 
 //(DocumentType)文檔類型節點 = nodeType:10
 console.log('文檔類型節點的節點名稱和值:' + document.doctype.nodeName + '/' + document.doctype.nodeValue);
 //nodeName:返回 document的名稱(html)。nodeValue:返回 null
 
 //(DocumentFragment)文檔片段節點 = nodeType:11
 var farg = document.createDocumentFragment();
 console.log('文檔片段節點的節點名稱和值:' + farg.nodeName + '/' + farg.nodeValue);
 //nodeName:返回 #document-fragment。nodeValue:返回 null
 </script>

 

2、DOMReady

  (1)、JS 在頁面中的位置

  頁面中的 JS 代碼,可以引入外部的 JS 文件,也可以放在 <head> 標簽中或 <body> 標簽中,放在 body 中的 JS 會在頁面加載的時候被執行,而 head 中的 JS 會在被調用的時候才執行。

  放在 <head> 標簽中或 <body> 標簽中 的區別:

  浏覽器解析 HTML 文檔是從上到下、從左到右依次進行的,如果把 JS 放在 head 裡的話,則先被解析,但這時候 body 還沒有被解析,所以會返回空值,也就是會出錯。放在 head 中的 JS 代碼會在頁面加載完成之前就讀取,而放在 body 中的 JS 代碼,會在整個頁面加載完成之後讀取。這就說明了,如果我們想定義一個全局對象,而這個對象是頁面中的某個按鈕時,我們必須將其放入 body 中,道理很明顯:如果放入head,那當你定義的時候,那個按鈕都沒有被加載,可能獲得的是一個 undefind。

  腳本應該放置的位置:

  頁面中的 JS 會在浏覽器加載頁面的時候被立即執行,有時候我們想讓一段腳本在頁面加載的時候執行,而有時候我們想在用戶觸發一個事件的時候執行腳本。

  需調用才執行的腳本或事件觸發執行的腳本放在 HTML 的 head 部分中,head 部分中的腳本,可以保證腳本在任何調用之前被加載。

  當頁面被加載時執行的腳本放在 HTML 的 body 部分。放在 body 部分的腳本通常被用來生成頁面的內容。

  body 和 head 部分可同時有腳本:文件中可以在 body 和 head 部分同時存在腳本。

  外部腳本:

  有時候需要在幾個頁面中運行同樣的腳本程序, 這時就需要用到外部腳本,而不需要在各個頁面中重復的寫這些代碼。

  (2)、JS 在頁面中的應用

  ①、放在 body 部分中:

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>JavaScript實例</title>
 </head>
 <body>
 <h1 id="main">這是h1標題中的一些文本。</h1>
 <script>
 document.getElementById('main').style.color = 'red';
 </script>
 </body>
 </html>

 

  上面的實例,通過 JS 改變了 h1 標題的 color 屬性,當打開頁面時標題顯示為紅色。

  ②、放在 head 部分中

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>JavaScript實例</title>
 <script>
 document.getElementById('main').style.color = 'red';
 </script>
 </head>
 <body>
 <h1 id="main">這是h1標題中的一些文本。</h1>
 </body>
 </html>

  將同樣的 JS 代碼放在 head 部分中,就無法正常運行了,浏覽器報錯:未捕獲的類型錯誤:不能讀取一個空元素的樣式屬性。出現這樣的錯誤就是沒有分清 HTML標簽和 DOM 節點之間的區別,HTML 是超文本標記語言,用於展示內容,而行為交互是需要通過 DOM 操作來實現的,HTML 標簽要通過浏覽器解析,才能變成 DOM 節點,當我們向地址欄輸入一個 URL 時,開始加載頁面,然後就能夠看到內容,在這期間就有一個 DOM 節點構建的過程,節點是以樹的形式組織的。JS 對於  DOM 的操作必須在 DOM 樹構建完畢後,而上面的實例,head 部分中的 JS 會被浏覽器先解析,但是這時候 body 中的元素還沒有被解析,DOM樹並沒有構建完畢,所以就出事了。那如果就想把 JS 代碼放在 head 部分中,還不想出錯,該怎麼解決呢?下文再做具體分析。

  (3)、DOMReady

  HTML 標簽需要通過浏覽器解析才會變成 DOM 節點,在刷新 URL 地址的時候就有 DOM 節點的構建過程,當頁面上所有的 HTML 標簽都轉換為節點以後,DOM 樹才構建完畢,這就簡稱為 DOMReady。

  那浏覽器是如何將 HTML 標簽解析變成節點的呢,浏覽器是通過渲染引擎來實現的,渲染引擎的職責就是渲染,即把請求的 HTML 內容顯示到浏覽器屏幕上。所謂渲染,就是浏覽器把請求到的 HTML 內容顯示在屏幕上的過程,所謂渲染引擎,就是浏覽器的內核,是浏覽器最核心的東西。

  各大浏覽器的渲染引擎:

  Firefox、Chrome 和 Safari 是基於兩種渲染引擎構建的:

  Firefox 使用 Geoko 內核,是 Mozilla 自主研發的渲染引擎,Gecko 是開源引擎,Gecko 也是一個跨平台內核,可以在Windows、Linux和Mac OS X等主要操作系統中運行。  

  Safari 和 Chrome 都使用 WebKit 內核,WebKit 和 WebCore 均是 KHTML 的衍生產品,KHTML 是 HTML 頁面渲染引擎之一,由 KDE 所開發。KHTML 擁有速度快捷的優點,對錯誤語法的容忍度要比 Mozilla 產品所使用的 Gecko 引擎小。WebKit 是一款開源渲染引擎,他本來是為 linux 平台研發的,後來由蘋果公司移植到 Mac 及 Windows上使用。

  而 IE 使用的是 Trident 內核(又稱為MSHTML),是微軟的視窗操作系統 (Windows) 搭載的網頁浏覽器 Internet Explorer 使用的渲染引擎,該內核程序在1997年的 IE 4 中首次被采用,之後不斷地加入新的技術並隨著新版本的 IE 發布,但是 Trident 只能用於 Windows 平台。

  渲染引擎在取得內容之後的基本流程:

  解析 HTML 以構建 DOM 樹  -> 構建 render 樹  ->  布局 render 樹  ->  繪制 render 樹

  渲染引擎開始解析 HTML,並將標簽轉化為一棵DOM樹,接著,他解析外部 CSS 文件及 style 標簽中的樣式信息,這些樣式信息以及 HTML 中的可見性指令將被用來構建另一棵樹 —— render 樹,用於渲染 DOM 樹的樹 —— 渲染樹(render tree)。

  render 樹由一些包含顏色和大小等屬性的矩形組成,他將被按照正確的順序顯示到屏幕上。

  render 樹構建好了之後,將會執行布局過程,它將確定每個節點在屏幕上的確切坐標。再下一步就是繪制,即遍歷 render 樹,並使用 UI 後端層繪制每個節點。

  值得注意的是,這個過程是逐步完成的,為了更好的用戶體驗,渲染引擎將會盡可能早的將內容呈現到屏幕上,並不會等到所有的 HTML 都解析完成之後再去構建和布局render 樹。他是解析完一部分內容就顯示一部分內容,同時,可能還在通過網絡下載其余內容。

  浏覽器打開頁面的常規流程:

  ①、浏覽器下載的順序是從上到下,渲染的順序也是從上到下,下載和渲染是同時進行的。

  ②、在渲染到頁面的某一部分時,其上面的所有部分都已經下載完成(並不是說所有相關聯的元素都已經下載完)。

  ③、如果遇到語義解釋性的標簽嵌入文件(JS 腳本,CSS 樣式),那麼此時IE的下載過程會啟用單獨連接進行下載。

  ④、並且在下載後進行解析,解析過程中,停止頁面所有往下元素的下載。

  ⑤、樣式表在下載完成後,將和以前下載的所有樣式表一起進行解析,解析完成後,將對此前所有元素(含以前已經渲染的)重新進行渲染。

  ⑥、JS、CSS中如有重復定義,則之後定義的函數將覆蓋前面定義的函數。

 

3、DOMReady 實現

  腳本在 HTML DOM 加載渲染布局顯示完成後才能運行,且要放在 body 部分內容的後面,而 DOMReady 則定義使腳本無論放在哪裡都能執行。

  (1)、使用定時器

  把 JS 代碼放在 head 部分中,還不想出錯,就是等 DOM 樹構建完畢後,再執行腳本,那麼是否可以使用定時器延遲腳本的執行,避免這個錯誤呢。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>JavaScript實例</title>
<script>
//在DomReady完畢後2秒執行。打開頁面可以看到一個樣式變化的過程。
setTimeout(function (){
    document.getElementById('main').style.color = 'red';
},2000);
</script>
</head>
<body>
<h1 id="main">這是h1標題中的一些文本。</h1>
</body>
</html>

   測試上面的代碼,可以看到,在打開頁面時標題顯示為黑色,過一會後才會轉變為紅色,雖然這樣不會報錯了,但這並不是我們想要的效果。那可以再將延遲時間縮短,不就解決了嗎,我們將時間設置為 30 毫秒再試試。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>JavaScript實例</title>
 <script>
 //將事件縮至30毫秒
 setTimeout(function (){
     document.getElementById('main').style.color = 'red';
 },30);
 </script>
 </head>
 <body>
 <h1 id="main">這是h1標題中的一些文本。</h1>
 </body>
 </html>

  測試上面代碼,在打開頁面時顯示為紅色,但如果刷新頁面的話,還是能看到黑色的閃動了一下,雖然無傷大雅,但這樣還存在著一個很嚴重的問題,如果 DomReady 時間超過了 30 毫秒,那還是會出錯,顯然這方法是不可行的。

  (2)、使用 window.onload

  window.onload 事件是在浏覽器繪制完 DOM 節點,再加載完頁面上的所有資源後,然後執行腳本。也就是說在文檔解析渲染,資源加載完成之前,不讓腳本執行。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>JavaScript實例</title>
 <script>
 window.onload = function (){
     document.getElementById('main').style.color = 'red';
 };
 </script>
 </head>
 <body>
 <h1 id="main">這是h1標題中的一些文本。</h1>
 </body>
 </html>

  測試上面的代碼,打開頁面後顯示為紅色,再刷新也不會出現黑色的閃動,顯然該方法是可行的。如果把 JS 代碼放在 head 部分中,一般情況下都需要綁定一個監聽,即window.onload 事件,等全部的 HTML 文檔渲染完成後,再執行代碼,這樣就妥妥的了。

  該方法在文檔外部資源不多的情況下,是沒什麼問題,但如果網站有很多圖片,我們要用 JS 做到在點擊每張圖片時彈出圖片的 src 屬性,這時候就有問題了,而且是出大事了,我們都知道 DOM 樹很快就構建完成了,但是這麼多圖片還在緩慢的加載中,想要先執行 JS 的效果,就得等到所有的圖片全部加載完畢後才能實現,而在這期間頁面不會響應用戶任何操作,浏覽器就跟死了一般。所以使用 window.onload 對於很多實際的操作(比如DOM操作,事件綁定等)就顯得太遲了,比如圖片過多,window.onload 卻遲遲不能觸發,影響用戶體驗。而 DOMReady 就可以滿足提前綁定事件的需求。

  (3)、jQuery 實現 DOMReady

  最簡單的方法就是使用 jQuery,jQuery 是一個 JS 函數庫,封裝了大量的 JS 方法,使用 jQuery 可以極大地簡化 JS 編程。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>JavaScript實例</title>
 <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
 <script>
 //jQuery 實現
 $(document).ready(function (){
     document.getElementById('main').style.color = 'red';
 });
 </script>
 </head>
 <body>
 <h1 id="main">這是h1標題中的一些文本。</h1>
 </body>
 </html>

 

  (4)、JS 實現 DOMReady

  用 JS 實現 DOMReady 其實也很簡單,可以添加一個監聽事件,即 addEventListener,該方法的語法為:document.addEventListener("事件名稱", 函數, false);,false 表示在冒泡階段捕獲。再傳入DOMContentLoaded 事件,這個事件是從 onload 事件延伸而來的,當一個頁面完成加載時,初始化腳本的方法是使用 onload 事件,該方法的缺點是僅在所有資源都完全加載後才被觸發,如果頁面的圖片很多的話,從用戶訪問到 onload 觸發可能需要較長的時間,所以開發人員隨後創建了一種自定義事件,DOMReady,他在 DOM 構建之後、資源加載之前就可以被觸發,他的表現形式就是 DOMContentLoaded 。jQuery 源碼中也是使用該方法完成的。

 <!DOCTYPE html>
 <html>
 <head>
     <meta charset="UTF-8">
     <title>JavaScript實例</title>
 <script>
 function domReady(fn){
     document.addEventListener('DOMContentLoaded', fn, false);
 }
 
 domReady(function (){
     document.getElementById('main').style.color = 'red';
 });
 </script>
 </head>
 <body>
 <h1 id="main">這是h1標題中的一些文本。</h1>
 </body>
 </html>

 

  上面代碼,我們給參數傳入一個回調函數,將 DOMReady 方法封裝為一個函數,方便以後使用,該方法支持所有現代浏覽器,但是不支持 IE9 之前的浏覽器。

 

4、HTML 嵌套規則

  了解 HTML 嵌套規則,是進行 DOM 操作的基礎。

  HTML 存在許多種類型的標簽,有的標簽下面只允許特定的標簽存在,這就叫 HTML 嵌套規則。

  如果不按 HTML 嵌套規則寫,浏覽器就不會正確解析,會將不符合嵌套規則的節點放到目標節點的下面,或者變成純文本。

  所以在編寫任何代碼時,都需要按照規則編寫,有利於解析,有利於操作,有利於優化,有利於維護,有利於重構。

  HTML 元素可簡單的分為塊狀元素和內聯元素兩類。下面是一些需要注意的嵌套規則:

  ①、塊元素可以包含內聯元素或某些塊元素,但內聯元素卻不能包含塊元素,他只能包含其他的內聯元素,li元素內可以包含 div 元素。

<div>
    <h1></h1>
    <p></p>
</div>

<a href=""><span class=""></span></a>

<ul>
    <li>
        <div></div>
    </li>
</ul>

 

  ②、塊元素不能包含在p元素內。

 <p>這是一些文本
     <div style="width:100px;height:200px;border:1px solid black;"></div>
 </p>
 
 <p>這是一些文本
     <ul>
         <li>蘋果</li>
         <li>香蕉</li>
     </ul>
 </p>

  測試上面的代碼,雖然他們都能被正常顯示,但是並不處在同一層。

  ③、有幾個特殊的塊元素只能包含內聯元素,不能包含塊元素,這幾個特殊的塊元素是 h1-h6、p 和 dt。

  ④、塊元素與塊元素並列,內聯元素與內聯元素並列。

 <div>
     <h2></h2>
     <p></p>
 </div>
 
 <div>
     <a href=""></a>
     <span class=""></span>
 </div>

 

5、DOM 操作

  DOM 節點是一個非常復雜的東西,對於他的每一個屬性的訪問,有可能會向上搜尋到 n 多個原型點,因此 DOM 操作是個很耗性能的操作。

  使用 DOM 操作很復雜,所以建議盡量使用現成的框架來實現業務,比如 MVVM 框架,將所有的 DOM 操作,都轉交給框架內部做精細有效的處理。

 

 

 

 

  

 

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved