JavaScript DOM操作及擴展
什麽是DOM???
DOM(Document Object Model 文檔對象模型)是針對HTML和XML文檔的一個API(應用程序編程接口)。
註意,IE中的所有DOM對象都是以COM(組件對象模型)對象的形式實現的。IE中的DOM對象與原生JavaScript對象的行為或活動特點並不一致。
COM對象是遵循COM規範編寫、以Win32動態鏈接庫(DLL)或可執行文件(EXE)形式發布的可執行二進制代碼,能夠滿足對組件架構的所有需求。DOM定義了一個Node接口,這個接口在JavaScript中是作為Node類型實現的,而在IE8-瀏覽器中的所有DOM對象都是以COM對象的形式實現的。所以,IE8-瀏覽器並不支持Node對象的寫法。
1.節點層次
首先,大家知道 “在Java的世界裏,一切皆對象“,同樣,Js 也是。但是,JavaScript並不具備傳統的面向對象語言所支持的類和接口等基本結構。DOM描繪了一個層次化的節點樹,允許開發人員添加、移除和修改頁面的某一部分。
DOM可以將任何HTML或XML文檔描繪成一個由多層節點構成的結構。節點分為幾種不同的類型,每種類型分別表示文檔中不同的信息及(或)標記。每個節點都擁有各自的特點、數據和方法,另外也與其他節點存在某種關系。節點之間的關系構成了層次,而所有頁面標記則表現為一個一特定節點為根結點的樹形結構。
以HTML為例:
文檔節點是每個文檔的根結點。在這個例子中,文檔節點只有一個子節點,即<html>元素,我們稱之為文檔元素。
文檔元素是文檔的最外元素,文檔中的其他所有元素都包含在文檔元素中。每個文檔只能有一個文檔元素。在HTML頁面中,文檔元素始終都是<html>元素。
每一段 標記都可以通過樹中的一個節點來表示:HTML元素通過元素節點表示,特性通過特性節點表示,文檔類型通過文檔類型節點表示,而註釋則通過註釋節點表示。
每個方框是文檔的節點,它表示一個Node對象。樹形的根部Document節點,它代表整個文檔。
2.節點類型
元素節點 Node.ELEMENT_NODE(1)
屬性節點 Node.ATTRIBUTE_NODE(2)
文本節點 Node.TEXT_NODE(3)
CDATA節點 Node.CDATA_SECTION_NODE(4)
實體引用名稱節點 Node.ENTRY_REFERENCE_NODE(5)
實體名稱節點 Node.ENTITY_NODE(6)
處理指令節點 Node.PROCESSING_INSTRUCTION_NODE(7)
註釋節點 Node.COMMENT_NODE(8)
文檔節點 Node.DOCUMENT_NODE(9)
文檔類型節點 Node.DOCUMENT_TYPE_NODE(10)
文檔片段節點 Node.DOCUMENT_FRAGMENT_NODE(11)
DTD聲明節點 Node.NOTATION_NODE(12)
Document
Document表示文檔,在瀏覽器中,document對象是HTMLDocument的一個實例,表示整個頁面,它同時也是window對象的一個屬性。Document有下面的特性:
(1)nodeType為9
(2)nodeName為#document
(3)nodeValue為null
(4)parentNode為null
(5)子節點可能是一個DocumentType或Element
Element
Element提供了對元素標簽名,子節點和特性的訪問,我們常用HTML元素比如div,span,a等標簽就是element中的一種。
Element有下面幾條特性:
(1)nodeType為1
(2)nodeName為元素標簽名,tagName也是返回標簽名
(3)nodeValue為null
(4)parentNode可能是Document或Element
(5)子節點可能是Element,Text,Comment,Processing_Instruction,CDATASection或EntityReference
Text
Text表示文本節點,它包含的是純文本內容,不能包含html代碼,但可以包含轉義後的html代碼。Text有下面的特性:
(1)nodeType為3
(2)nodeName為#text
(3)nodeValue為文本內容
(4)parentNode是一個Element
(5)沒有子節點
Attr
Attr類型表示元素的特性,相當於元素的attributes屬性中的節點,它有下面的特性:
(1)nodeType值為2
(2)nodeName是特性的名稱
(3)nodeValue是特性的值
(4)parentNode為null
Comment
Comment表示HTML文檔中的註釋,它有下面的幾種特征:
(1)nodeType為8
(2)nodeName為#comment
(3)nodeValue為註釋的內容
(4)parentNode可能是Document或Element
(5)沒有子節點
DocumentFragment類型
DocumentFragment是所有節點中唯一一個沒有對應標記的類型,它表示一種輕量級的文檔,可能當作一個臨時的倉庫用來保存可能會添加到文檔中的節點。DocumentFragment有下面的特性:
(1)nodeType為11
(2)nodeName為#document-fragment
(3)nodeValue為null
(4)parentNode為null
節點創建型
createElement
createElement通過傳入指定的一個標簽名來創建一個元素,如果傳入的標簽名是一個未知的,則會創建一個自定義的標簽,註意:IE8以下瀏覽器不支持自定義標簽。
使用如下:
var div = document.createElement("div");
使用createElement要註意:通過createElement創建的元素並不屬於html文檔,它只是創建出來,並未添加到html文檔中,要調用appendChild或insertBefore等方法將其添加到HTML文檔樹中。
createTextNode大致同上
createTextNode接收一個參數,這個參數就是文本節點中的文本,和createElement一樣,創建後的文本節點也只是獨立的一個節點,同樣需要appendChild將其添加到HTML文檔樹中
cloneNode
cloneNode是用來返回調用方法的節點的一個副本,它接收一個boolean參數,用來表示是否復制子元素,使用如下:
var parent = document.getElementById("parentElement");
var parent2 = parent.cloneNode(true);// 傳入true
parent2.id = "parent2";
這段代碼通過cloneNode復制了一份parent元素,其中cloneNode的參數為true,表示parent的子節點也被復制,如果傳入false,則表示只復制了parent節點。
這段代碼很簡單,主要是綁定button事件,事件內容是復制了一個parent,修改其id,然後添加到文檔中。這裏有幾點要註意:
(1)和createElement一樣,cloneNode創建的節點只是遊離有html文檔外的節點,要調用appendChild方法才能添加到文檔樹中
(2)如果復制的元素有id,則其副本同樣會包含該id,由於id具有唯一性,所以在復制節點後必須要修改其id
(3)調用接收的bool參數最好傳入,如果不傳入該參數,不同瀏覽器對其默認值的處理可能不同
除此之外,我們還有一個需要註意的點:如果被復制的節點綁定了事件,則副本也會跟著綁定該事件嗎?這裏要分情況討論:(1)如果是通過addEventListener或者比如onclick進行綁定事件,則副本節點不會綁定該事件(2)如果是內聯方式綁定比如<div onclick="showParent()"></div>這樣的話,副本節點同樣會觸發事件。
createDocumentFragment
createDocumentFragment方法用來創建一個DocumentFragment。前面說到的DocumentFragment表示一種輕量級的文檔,它的作用主要是存儲臨時的節點用來準備添加到文檔中。
這段代碼將按鈕綁定了一個事件,這個事件創建了100個li節點,然後依次將其添加HTML文檔中。這樣做有一個缺點:每次一創建一個新的元素,然後添加到文檔樹中,這個過程會造成瀏覽器的回流。所謂回流簡單說就是指元素大小和位置會被重新計算,如果添加的元素太多,會造成性能問題。這個時候,就是使createDocumentFragment了。
創建型 總結
創建型API主要包括createElement、createTextNode、cloneNode、和createDocumentFragment四個方法,需要註意下面幾點:
(1)它們創建的節點只是一個孤立的節點,要通過appendChild添加到文檔中。
(2)cloneNode要註意如果被復制的節點是否包含子節點以及事件綁定等問題
(3)使用createDocumentFragment來解決添加大量節點時的性能問題
修改型
修改頁面內容的API主要包括:appendChild,insertBefore,removeChild,replaceChild。
appendChild
appendChild我們在前面已經用到多次,就是將指定的節點添加到調用該方法的節點的子元素的末尾。調用方法如下:parent.appendChild(child);child節點將會作為parent節點的最後一個子節點這個方法很簡單,但是有一點需要註意:如果被添加的節點是一個頁面中存在的節點,則執行後這個節點將會添加到指定位置,其原本所在的位置將移除該節點,也就是說不會同時存在兩個該節點在頁面上,相當於把這個節點移動到另一個地方。
這段代碼主要是獲取頁面上的child節點,然後添加到指定位置,可以看到原本的child節點被移動到parent中了。這裏還有一個要註意的點:如果child綁定了事件,被移動時,它依然綁定著該事件。
insertBefore
insertBefore用來添加一個節點到一個參照節點之前,用法如下:
parentNode.insertBefore(newNode,refNode);
parentNode表示新節點被添加後的父節點
newNode表示要添加的節點
refNode表示參照節點,新節點會添加到這個節點之前
關於第二個參數參照節點還有幾個註意的地方:
(1)refNode是必傳的,如果不傳該參數會報錯
(2)如果refNode是undefined或null,則insertBefore會將節點添加到子元素的末尾
removeChild
removeChild顧名思義,就是刪除指定的子節點並返回,用法如下:
var deletedChild = parent.removeChild(node);
deletedChild指向被刪除節點的引用,它等於node,被刪除的節點仍然存在於內存中,可以對其進行下一步操作。
註意:如果被刪除的節點不是其子節點,則程序將會報錯。我們可以通過下面的方式來確保可以刪除:
if(node.parentNode){
node.parentNode.removeChild(node);
}
通過節點自己獲取節點的父節點,然後將自身刪除。
replaceChild
replaceChild用於使用一個節點替換另一個節點,用法如下:parent.replaceChild(newChild,oldChild);
newChild是替換的節點,可以是新的節點,也可以是頁面上的節點,如果是頁面上的節點,則其將被轉移到新的位置,oldChild是被替換的節點。
頁面修改型api主要是這四個接口,要註意幾個特點:
(1)不管是新增還是替換節點,如果新增或替換的節點是原本存在頁面上的,則其原來位置的節點將被移除,也就是說同一個節點不能存在於頁面的多個位置
(2)節點本身綁定的事件不會消失,會一直保留著。
節點查詢型API
document.getElementById
這個接口很簡單,根據元素id返回元素,返回值是Element類型,如果不存在該元素,則返回null。使用這個接口有幾點要註意:
(1)元素的Id是大小寫敏感的,一定要寫對元素的id
(2)HTML文檔中可能存在多個id相同的元素,則返回第一個元素
(3)只從文檔中進行搜索元素,如果創建了一個元素並指定id,但並沒有添加到文檔中,則這個元素是不會被查找到的
document.getElementsByTagName
這個接口根據元素標簽名獲取元素,返回一個即時的HTMLCollection類型,什麽是即時的HTMLCollection類型呢?
這個方法有幾點要註意:
(1)如果要對HTMLCollection集合進行循環操作,最好將其長度緩存起來,因為每次循環都會去計算長度,暫時緩存起來可以提高效率
(2)如果沒有存在指定的標簽,該接口返回的不是null,而是一個空的HTMLCollection
(3)“ * ”表示所有標簽
一個按鈕是顯示HTMLCollection元素的個數,另一個按鈕可以新增一個div標簽到文檔中。前面提到HTMLCollcetion元素是即時的表示該集合是隨時變化的,也就是是文檔中有幾個div,它會隨時進行變化,當我們新增一個div後,再訪問HTMLCollection時,就會包含這個新增的div。
document.getElementsByName
getElementsByName主要是通過指定的name屬性來獲取元素,它返回一個即時的NodeList對象。使用這個接口主要要註意幾點:
(1)返回對象是一個即時的NodeList,它是隨時變化的
(2)在HTML元素中,並不是所有元素都有name屬性,比如div是沒有name屬性的,但是如果強制設置div的name屬性,它也是可以被查找到的
(3)在IE中,如果id設置成某個值,然後傳入getElementsByName的參數值和id值一樣,則這個元素是會被找到的,所以最好不好設置同樣的值給id和name
擴展
一.選擇符API
1.querySelector()方法
該方法接收一個CSS選擇符,返回與該模式匹配的第一個元素,如果沒有找到匹配的元素,返回null。
通過Document類型調用querySelector()方法時,會在文檔元素的範圍內查找匹配的元素。而通過Element類型調用querySelector()方法時,只會在該元素後代元素的範圍內查找匹配的元素。
註:眾多JavaScript庫中最常用的一項功能,就是根據CSS選擇符選擇與某個模式匹配的DOM元素。實際上,jQuery的核心就是通過CSS選擇符查詢DOM文檔取得元素的引用,從而拋開了getElementById()和getElementByTagName()。
CSS選擇符可以簡單也可以復雜,視情況而定。如果傳入了不被支持的選擇符,querySelector()會拋出錯誤。
2.querySelectorAll()
querySelectorAll()方法接受的參數與querySelector()方法一樣,都是一個CSS選擇符,但是返回的不僅不僅是一個元素,而是一個NodeList的實例。
與querySlector()一樣能夠調querySlectorAll()方法的有document,element,DocumentFragment。
要取得返回的NodeList中的每一個元素,可以使用item()方法,也可以使用方括號語法,比如:
getElementsByClassName()
1)使用方法:element.getElementsByClassName("classNames"),其中,element是有效的DOM元素(包括document)
classNames是CSS類名稱的組合(多個類名之間用空格,可以是多個空格隔開),如
element.getElementsByClassName("class2 class1")
將選取elements後代元素中同時應用了class1和class2樣式的元素(樣式名稱不區分先後順序) 2)說明:a. 返回值是一個nodeList集合(區別於Array 有明確的定義與概念)
b. 該方法只能選取調用該方法的元素的後代元素。
3)兼容性:IE8及其以下版本的瀏覽器未實現getElementsByClassName方法
HTML5添加的getElementsByClassName()方法,可以通過Document對象及所有HTML元素調用該方法
事件
所謂事件,就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間
一、事件流
在頁面上,單擊某個元素的同時,也單擊了它的包含容器。事件流就是描述的從頁面中接收事件的順序。IE是事件冒泡流,Netscape是事件捕獲流。
事件冒泡
事件開始時,由最具體的元素(文檔中嵌套最深的那個節點)接收,然後逐級向上傳播到較為不具體的節點(文檔);(所有現代瀏覽器都支持事件冒泡) 如果單擊了頁面中的<div>元素,那麽這個click事件會按照如下順序傳播:
(1)<div>
(2)<body>
(3)<html>
(4)document
事件捕獲
不太具體的節點最早接收到事件,最具體的節點最後接收到事件。(老版本瀏覽器不支持)如果單擊了頁面中的<div>元素,那麽這個click事件會按照如下順序傳播:
(1)document
(2)<html>
(3)<body>
(4)<div>
二、DOM事件流
DOM2級事件規定的事件流包括三個階段:
1、事件捕獲階段
2、處於目標階段
3、事件冒泡階段
在DOM事件流中,實際的目標(div元素)在捕獲階段不會接受到事件。這意味著在捕獲階段,事件從document到<html>再到<body>後就停止了。下一階段是”處於目標”階段,於是事件在<div>上發生,並在事件處理中被看成冒泡階段的一部分。然後,冒泡階段發生,事件又傳播回文檔。
多數支持DOM事件流的瀏覽器都實現了一種特定的行為:即使”DOM2級事件”規範明確要求捕獲階段不會涉及事件目標,但IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都會在捕獲階段觸發事件對象上的事件。結果,就是有兩個機會在目標對象上面操作事件。
瀏覽器的內核
主要分成兩部分:渲染引擎(layout engineer或Rendering Engine)和JS引擎。渲染引擎:負責取得網頁的內容(HTML、XML、圖像等等)、整理訊息(例如加入CSS等),以及計算網頁的顯示方式,然後會輸出至顯示器或打印機。瀏覽器的內核的不同對於網頁的語法解釋會有不同,所以渲染的效果也不相同。所有網頁瀏覽器、電子郵件客戶端以及其它需要編輯、顯示網絡內容的應用程序都需要內核。JS引擎則:解析和執行javascript來實現網頁的動態效果。最開始渲染引擎和JS引擎並沒有區分的很明確,後來JS引擎越來越獨立,內核就傾向於只指渲染引擎。
解析html以構建dom樹 -> 構建render樹 -> 布局render樹 -> 繪制render樹。
當瀏覽器獲得一個html文件時,會“自上而下”加載,並在加載過程中進行解析渲染。
解析:
1. 瀏覽器會將HTML解析成一個DOM樹,DOM 樹的構建過程是一個深度遍歷過程:當前節點的所有子節點都構建好後才會去構建當前節點的下一個兄弟節點。
2. 將CSS解析成 CSS Rule Tree 。
3. 根據DOM樹和CSSOM來構造 Rendering Tree。
註意:Rendering Tree 渲染樹並不等同於 DOM 樹,因為一些像 Header 或 display:none 的東西就沒必要放在渲染樹中了。
4.有了Render Tree,瀏覽器已經能知道網頁中有哪些節點、各個節點的CSS定義以及他們的從屬關系。下一步操作稱之為Layout,顧名思義就是計算出每個節點在屏幕中的位置。
5.再下一步就是繪制,即遍歷render樹,並使用UI後端層繪制每個節點。
上述過程是逐步完成的,為了更好的用戶體驗,渲染引擎將會盡可能早的將內容呈現到屏幕上,並不會等到所有的html都解析完成之後再去構建和布局render樹。它是解析完一部分內容就顯示一部分內容,同時,可能還在通過網絡下載其余內容。
(1)Reflow(回流):瀏覽器要花時間去渲染,當它發現了某個部分發生了變化影響了布局,那就需要倒回去重新渲染。
(2)Repaint(重繪):如果只是改變了某個元素的背景顏色,文字顏色等,不影響元素周圍或內部布局的屬性,將只會引起瀏覽器的repaint,重畫某一部分。
Reflow要比Repaint更花費時間,也就更影響性能。所以在寫代碼的時候,要盡量避免過多的Reflow。
reflow的原因:
(1)頁面初始化的時候;
(2)操作DOM時;
(3)某些元素的尺寸變了;
(4)如果 CSS 的屬性發生變化了。
減少 reflow/repaint
(1)不要一條一條地修改 DOM 的樣式。與其這樣,還不如預先定義好 css 的 class,然後修改 DOM 的 className。
(2)不要把 DOM 結點的屬性值放在一個循環裏當成循環裏的變量。
(3)為動畫的 HTML 元件使用 fixed 或 absoult 的 position,那麽修改他們的 CSS 是不會 reflow 的。
(4)千萬不要使用 table 布局。因為可能很小的一個小改動會造成整個 table 的重新布局。
編寫CSS時應該註意:
CSS選擇符是從右到左進行匹配的。從右到左,所以,#nav li 我們以為這是一條很簡單的規則,秒秒鐘就能匹配到想要的元素,但是,但是,但是,是從右往左匹配啊,所以,會去找所有的li,然後再去確定它的父元素是不是#nav。,因此,寫css的時候需要註意:
(1)dom深度盡量淺。
(2)減少inline javascript、css的數量。
(3)使用現代合法的css屬性。
(4)不要為id選擇器指定類名或是標簽,因為id可以唯一確定一個元素。
(5)避免後代選擇符,盡量使用子選擇符。
原因:子元素匹配符的概率要大於後代元素匹配符。後代選擇符;#tp p{} 子選擇符:#tp>p{}
(6)避免使用通配符,舉一個例子:
.mod .hd *{font-size:14px;}
根據匹配順序,將首先匹配通配符,也就是說先匹配出通配符,然後匹配.hd(就是要對dom樹上的所有節點進行遍歷他的父級元素),然後匹配.mod,這樣的性能耗費可想而知.
CSDN:http://blog.csdn.net/qq_33430445/article/details/76977623
JavaScript DOM操作及擴展