首頁>技術>

本內容是《Web前端開發之Javascript影片》的課件,請配合大師哥《Javascript》影片課程學習。

DOM(Document Object Model,文件物件模型):

DOM是一個使程式和指令碼能夠動態地訪問和更新文件的內容、結構以及樣式,並獨立於平臺和語言的介面;其定義了訪問和處理文件的標準方法;是針對HTML和XML文件的一個API;

DOM描繪了一個層次化的節點樹,允許開發者新增、移除和修改頁面的某一部分,從而重構整個文件。

DOM脫胎於Netscape及微軟創始的DHTML,但現在已經成為表現和操作頁面標記的真正的跨平臺、語言中立的方式;

DOM與具體的程式語言無關,可以在C、Javascript、ActionScript、Java等語言中實現;

DOM標準的目標是讓“任何一種程式設計語言”能操控使用“任何一種標記語言”編寫出的“任何一份文件”;

DOM 級別:

DOM Level 1:由兩個模組組成:DOM核心(DOM Core)和DOM HTML;專注於 HTML 和 XML 文件模型,它含有文件導航和處理功能;

DOM Level 2:對 DOM1級做了擴充套件,添加了樣式表物件模型,並定義了操作附於文件之上的樣式資訊的功能性;同時還定義了一個事件模型,並提供了對 XML 名稱空間的支援;

DOM Level 3:對DOM2級做了擴充套件,規定了內容模型 (DTD 和 Schemas) 和文件驗證,同時規定了文件載入和儲存、文件檢視、文件格式化和關鍵事件;

DOM Level 0:確切來說,不存在DOM0級,因為它不是 W3C 規範,而僅僅是對在 Netscape Navigator 3.0 和 Microsoft Internet Explorer 3.0 中的等價功能性的一種定義,實際上指的就是DHTML;

1998年10月 DOM1級規範成為W3C的標準,為基本的文件結構及查詢提供了介面;

目前主流的瀏覽器都已實現了DOM1、基本實現了DOM2和3;

DOM組成:

Core DOM:定義了一套標準的針對任何結構化文件的物件,即用於XML和HTML的共用介面;HTML DOM:在 DOM 核心的基礎上加以擴充套件,定義了一套標準的針對HTML文件的介面物件;

XML:

XML 指可擴充套件標記語言(EXtensible Markup Language),它是一種標記語言,類似於HTML,它的設計宗旨是傳輸資料,而非顯示資料;

XML 標籤沒有被預定義,需要開發者自定義標籤;

XML 被設計為具有自我描述性,是W3C的推薦標準;

XML 的用途:把資料從 HTML 分離、簡化資料共享、簡化資料傳輸、簡化平臺的變更;

DOM樹:

DOM可以將任何HTML或XML文件描繪成一個由多層節點構成的結構;

如HTML:

樹狀圖

每一個標籤是是文件的一個節點,它表示一個Node物件;

節點分為幾種不同的型別,每種型別分別表示文件中不同的資訊或標記;

每種節點都擁有各自的特點、資料和方法,並且也與其他節點存在某種關係;

節點之間的關係構成了層次,而所有頁面標記則表現為一個以特定節點為根節點的樹形結構;

文件節點(Document):是每個文件的根節點;文件節點只有一個子節點,即<html>元素,也稱為文件元素,它是文件最外層元素,其他所有元素都包含在文件元素中;每個文件只能有一個文件元素;在XML中,沒有預定義的文件元素,因此任何元素都可能成為文件元素;

Node介面:

每一段標記都可以透過樹中的一個節點來表示,這個節點稱為Node;

DOM1級定義一個Node介面,其用於抽象地表示文件中一個獨立的部分;

HTML元素透過元素節點表示,特性(attribute)透過特性節點表示,文件型別透過文件型別節點表示,註釋透過註釋節點表示,共12種節點型別,這些型別都繼承自一個基型別Node型別,因此所有節點型別都共享著相同的基本屬性和方法;

Node型別圖:

Node型別圖

Document和Element型別與HTMLDocument和HTMLElement型別之間是有嚴格的區分的;Document型別代表一個HTML或XML文件,而HTMLDocment只是代表一個HTML文件,XMLDocument代表是XML文件;Element型別代表該文件中的一個元素,而HTMLElement只是HTML文件中的元素,不是XMLDocument中的元素;

HTMLElement的很多子型別代表HTML元素的具體型別,每個型別具有多個Javascript屬性,這些屬性對應具體的元素或元素組的HTML元素特性;這些具體的元素類也定義了額外的屬性和方法,它們並不是簡單的對映HTML元素及HTML元素特性;

每個節點都有一個nodeType屬性,用於表明節點的型別;

節點型別由在Node型別中定義的下列12個常數值來表示;

nodeName : String,節點的名字,取決於節點的型別;

nodeValue : String,節點的值,取決於節點的型別;

nodeType : Number,節點的型別常數值之一;

注:對於元素節點,nodeName儲存的始終是元素的標籤名,而nodeValue的值為null;因此在使用nodeName及nodeValue時,最好先檢測一下節點的型別;

childNodes屬性:

返回NodeList型別的所有子節點集合;

NodeList是一種類陣列的物件,用於儲存一組有序的節點,可以透過方括號來訪問儲存在其中的節點,其擁有length 屬性;

NodeList物件的特點:是基於DOM結構動態執行查詢的結果,因此DOM結構的變化能夠自動反映在NodeList物件中;

var mydiv = document.getElementById("mydiv");console.log(mydiv.childNodes.length); // 7var p = document.createElement("p");p.innerText = "大師哥王唯";mydiv.appendChild(p);console.log(mydiv.childNodes.length);  // 8

在實際應用中,有可能需要將NodeList物件轉換成陣列:

var mydiv = document.getElementById("mydiv");var arrNodes = Array.prototype.slice.call(mydiv.childNodes,0);console.log(arrNodes);

children屬性:

IE與其他瀏覽器對文字節點中空白符的解釋不一致,導致了children屬性出現;這個屬性是HTMLCollection的例項,其中只包含元素的子節點中那些也是元素的節點,即children列表中只包含Element元素;

children並不是標準屬性,但所有的瀏覽器都實現了該屬性;

var myList = document.getElementById("myList");var lis = myList.children;console.log(lis);console.log(lis.length);

Text和Comment節點沒有children屬性;

HTMLCollection型別:

是一個介面,表示HTML 元素的集合,與NodeList非常類似,也是個類陣列物件;元素在 HTMLCollection 物件中的順序與它們在文件原始碼中出現的順序一樣;

HTMLCollection型別的屬性和方法:

item()方法:返回 HTMLCollection 中指定索引的元素;

length屬性:返回 HTMLCollection 中元素的數量;

namedItem()方法:返回 HTMLCollection 中指定 ID 或 name 屬性的元素;

console.log(lis.item(0));console.log(lis[0]);  // 一般用這個代替console.log(lis.namedItem("myli"));console.log(lis["myli"]);  // 一般用這個代替

與NodeList型別一樣,HTMLCollection物件也是實時動態的;

parentNode屬性:

指向節點的父節點;

一個元素節點的父節點可能是一個元素(Element )節點,也可能是一個文件(Document )節點,或者是個文件碎片(DocumentFragment)節點;

對於Document、DocumentFragment和Attr物件來說,其parentNode屬性為null,因為它們沒有父節點;

另外,如果當前節點剛剛被建立,還沒有被插入到DOM樹中,則該節點的parentNode屬性也返回null;

var elt = document.getElementById("elt");if(elt.parentNode){    elt.parentNode.removeChild(elt);}

parentElement屬性:

返回當前節點的父元素節點,如果該元素沒有父節點,或者父節點不是一個 DOM 元素,則返回 null;parentElement是一個DOM元素物件(HTMLElement物件);

if(elt.parentElement)    elt.parentElement.style.backgroundColor = "purple";

在早期,parentElement是ie專用的,而現在所有的瀏覽器都已經實現了,並且被納入了最新的DOM4規範中;

parentElement匹配的是parent為Element的情況,而parentNode匹配的則是parent為Node的情況;Element是包含在Node裡的,它的nodeType是1;一般情況parentNode可以取代parentElement的所有功能;

最能體現兩者的區別是以下兩行程式碼:

console.log(document.documentElement);  // <html>console.log(document.documentElement.parentNode);  // #documentconsole.log(document.documentElement.parentElement);  // null

previousSibling: 前一個兄弟節點;如果這個節點就是第一個兄弟節點,那麼該值為null;

nextSibling : 後一個兄弟節點:如果這個節點就是最後一個兄弟節點,那麼該值為null;

var mydiv = document.getElementById("mydiv");console.log(mydiv.parentNode);  // <body>var firstChild = mydiv.childNodes[0];  // 把0換成1或2console.log(firstChild.previousSibling);console.log(firstChild.nextSibling);if(firstChild.nextSibling === null)    console.log("child is last node");elseconsole.log("child is't last node");// 遍歷var elt = document.getElementById("elt");while(elt){    console.log(elt.nodeName);    elt = elt.nextSibling;}

firstChild : 指向在childNodes集合中的第一個節點;

lastChild : 指向在childNodes集合中最後一個節點;

即firstChild始終指向childNodes[0];lastChild指向childNodes[someNode.childNodes.length - 1];

var mydiv = document.getElementById("mydiv");var firstChild = mydiv.firstChild;var lastChild = mydiv.lastChild;console.log(firstChild);console.log(lastChild);

注:當只有一個子節點的情況下,firstChild和lastChild指向同一個節點;如果沒有子節點,均為null;並不是每種節點都有子節點;

<div id="mydiv"><h2>零點<span>程式設計師</span></h2></div><script>    var textChild = mydiv.firstChild.firstChild;    console.log(textChild.nodeType);  // 3 文字節佔    console.log(textChild.childNodes); // 空的NodeList[]</script>

textChild指的是零點,而不是零點<span>程式設計師</span>;

ownerDocument 屬性:

指向這個節點所屬的文件節點(也就是頂層節點);

任何節點都屬於它所在的文件,任何節點都不能同時存在於兩個或多個文件中,透過這個屬性不必層層回溯到頂端,而是直接訪問到文件節點;

var mydiv = document.getElementById("mydiv");console.log(mydiv.ownerDocument); // #documentvar innerDiv = document.getElementById("innerDiv");console.log(innerDiv.ownerDocument); // #documentconsole.log(mydiv.ownerDocument.documentElement);  // <html>

節點層次關係圖:

節點層次關係圖

Node方法(操作節點):

appendChild(node) : 將node新增到childNodes的末尾; 新增節點後,childNodes的新添節點、父節點及以前的最後一個子節點的關係指標都會相應地得到更新;更新後,appendChild()返回新增的節點;

var mydiv = document.getElementById("mydiv");var p = document.createElement("p");p.innerText = "大師哥王唯";var returnP = mydiv.appendChild(p);console.log(mydiv.lastChild);  // <p>大師哥王唯</p>console.log(p === returnP);  // true

如果傳入appendChild()的節點已經是文件的一部分了,就將該節點從原來的位置轉移到新位置;即任何DOM節點不能同時出現在文件中的多個位置上;

<script>var mydiv = document.getElementById("mydiv");var yourdiv = document.getElementById("yourdiv");// 注:把最後的子元素的空白符刪除var returnDiv = yourdiv.appendChild(mydiv.lastChild);console.log(returnDiv === mydiv.lastChild);  // falseconsole.log(returnDiv === yourdiv.firstChild);  // true</script>

如果在呼叫appendChild()方法時,傳入了父節點的第一個子節點,那麼,該節點就會成為父節點中的最後一個子節點,如:

// 注意html中的換行空格var mydiv = document.getElementById("mydiv");console.log(mydiv.firstChild);mydiv.appendChild(mydiv.firstChild);console.log(mydiv.firstChild);

需要注意的問題;

var divs = document.getElementsByTagName("div");var btn = document.createElement("input");btn.type = "button";btn.value = "按鈕";for(var i=0; i<divs.length; i++){    console.log(divs[i]);    divs[i].appendChild(btn);}

本來的意圖是為了給所有的div新增input子元素,可卻終只是最後的div添加了,原因是一個元素只能有一個父元素,起先,第一個div的確添加了input的元素,便是迴圈中的appendChild()會讓元素從原來的位置轉移到新位置;

改寫:把建立btn的程式碼放到for迴圈內,即可達到目的;

由於appendChild()返回的是被追加的子元素,所以在鏈式呼叫時不能隨便使用;

var elt = document.createElement('p').appendChild(document.createElement('b'));console.log(elt);  // elt為<b></b>

本意是返回一個p節點,並且這個p元素包含一個b的子節點,但實際上elt為b;

// 改成var elt = document.createElement('p')elt.appendChild(document.createElement('b'));console.log(elt);  // <p>document.body.appendChild(elt);

insertBefore(newnode, refnode)方法:

在childNodes中的refnode之前插入newcode,並返回這個節點;

插入節點後,被插入的節點(新節點)會變成參照節點的前一個兄弟節點;如果參照節點是null,則與appendChild()執行相同的操作;

var mydiv = document.getElementById("mydiv");var p = document.createElement("p");p.innerText = "大師哥王唯";// 或者參照mydiv.firstChild節點var returnP = mydiv.insertBefore(p,null);  // 插入後成為最後一個子節點console.log(mydiv.lastChild);  // <p>大師哥王唯</p>var returnP = mydiv.insertBefore(p, mydiv.firstChild);  // 插入後成為第一個子節點console.log(mydiv.firstChild);  // <p>大師哥王唯</p>var returnP = mydiv.insertBefore(p, mydiv.lastChild);  // 插入到最後子節點的前面console.log(mydiv.childNodes[mydiv.childNodes.length - 2]);// <p>大師哥王唯</p>var returnP = mydiv.insertBefore(p,mydiv.childNodes[1]);  // 插入到任意節點的前面console.log(mydiv.childNodes[1]);  // <p>大師哥王唯</p>var yourdiv = document.getElementById("yourdiv");yourdiv.insertBefore(mydiv.childNodes[1],null);//把mydiv的子節點插入到yourdiv中

參照節點refnode是必選引數,如果沒有,則傳null,否則會丟擲異常;如果傳遞undefined,則會隱式轉換;

// mydiv.insertBefore(elt); // 異常mydiv.insertBefore(elt,undefined); // undefined隱式轉換// mydiv.insertBefore(elt,"undefined"); // 異常

如果給定的子節點是文件中現有的節點,insertBefore() 會將其從當前位置移動到新位置;

var elt = document.getElementById("elt");var mydiv = document.getElementById("mydiv");mydiv.insertBefore(elt,mydiv.firstChild); // elt被移到mydiv內的第一個位置

提供一個使用索引位置插入節點的簡單函式;

移除子節點,並返回被移除節點

var mydiv = document.getElementById("mydiv");var deleteChild = mydiv.removeChild(mydiv.firstChild);console.log(deleteChild);// 刪除的節點還可以再次使用var yourdiv = document.getElementById("yourdiv");yourdiv.appendChild(deleteChild);console.log(yourdiv.firstChild);

被移除的節點仍然還在文件中,但它在文件中已經沒有了自己的位置,但如果被刪除節點沒有變數引用它,在一定的時間內將會被記憶體管理器回收;

var mydiv = document.getElementById("mydiv");var deleteChild = mydiv.removeChild(mydiv.firstChild);console.log(deleteChild === mydiv.firstChild);  // false// 改成var deleteChild = mydiv.firstChild;var returnChild = mydiv.removeChild(deleteChild);console.log(deleteChild === returnChild); // true

removeChild()方法是在父節點上呼叫的,所以刪除一個節點,一定先定位好父節點再刪除子節點,比如要刪除當前的元素,可以:

var mydiv = document.getElementById("mydiv");for(var i=mydiv.childNodes.length-1; i>=0; i--){    console.log(mydiv.childNodes[i]);    mydiv.removeChild(mydiv.childNodes[i]);}console.log(mydiv);

為什麼由後往前刪除,因為childNodes返回的列表是動態的,每一次訪問它都是被刪除一個後的列表;因此,如果只是單純的刪除所有子節點,可以:

// 更簡單的刪除,或者把firstChild換成lastChild也可以while(mydiv.firstChild)    mydiv.removeChild(mydiv.firstChild);

replaceChild(newnode, oldnode) 方法:

<div id="yourdiv">temp</div><script>var mydiv = document.getElementById("mydiv");var p = document.createElement("p");p.innerText = "大師哥王唯";var child = mydiv.replaceChild(p, mydiv.firstChild);console.log(child);var yourdiv = document.getElementById("yourdiv");if(yourdiv.hasChildNodes()){    var child = yourdiv.replaceChild(mydiv.lastChild,yourdiv.firstChild);}</script>

定義一個環繞節點的函式:

function roundNode(outerNodeString,innerNode){    // 假如引數為字串而不是節點,將其當做元素的id    if(typeof innerNode == "string")        innerNode = document.getElementById(innerNode);    var parent = innerNode.parentNode;  // 取得父節點    var outerNode = document.createElement(outerNodeString);    parent.replaceChild(outerNode, innerNode);    outerNode.appendChild(innerNode);    return outerNode;}var mydiv = document.getElementById("mydiv");roundNode("div",mydiv).style.color = "red";

cloneNode()方法:

用於建立呼叫這個方法的節點的一個完全相同的副本;

其接受一個引數,表示是否執行深複製,true為深複製,即複製節點及其整個子節點樹; false,則為淺複製,即只複製節點本身,節點所包含的文字也不會被複制,預設為false;

var mydiv = document.getElementById("mydiv");var cloneDiv = mydiv.cloneNode(true);console.log(cloneDiv);

<ul id="mylist">    <li>HTML5</li>    <li>CSS3</li>    <li>Javascript</li></ul><script>var myList = document.getElementById("mylist");var deepList = myList.cloneNode(true);var shallowList = myList.cloneNode(false);console.log(deepList.childNodes.length);  // 7console.log(shallowList.childNodes.length); // 0</script>

複製後的節點副本屬於文件所有,但並沒有父節點,因此需要透過appendChild()、insertBefore或replaceChild將它新增文件中;

console.log(mydiv.parentNode);  // <body>console.log(cloneDiv.parentNode);  // nullvar yourdiv = document.getElementById("yourdiv");yourdiv.appendChild(cloneDiv);  // 不符合標準,因為有兩個id為mydiv元素console.log(cloneDiv.parentNode);  // <div id="yourdiv">

cloneNode()方法不會複製新增到DOM節點中的Javascript屬性,如事件處理程式等,但會複製特性、子節點;但如果是在標籤中直接新增的on事件,也會被複制,因為它是被當作特性複製的;

var myList = document.getElementsByTagName("ul")[0];var li3 = myList.childNodes[5]; // 第3個lili3.addEventListener("click",function(e){    alert(this.innerText);},false);var deepList = myList.cloneNode(true);var yourdiv = document.getElementById("yourdiv");yourdiv.appendChild(deepList);

如果原始節點設定了ID,並且克隆節點會被插入到相同的文件中,那麼就應該修改克隆節點的ID以保證其唯一性;

console.log(deepList.id);  // mylistdeepList.id = "deeplist";console.log(deepList.id);  // deeplist

normalize()方法:

合併節點,該方法唯一的作用就是處理文件樹中的文字節點;由於解析器的實現或DOM操作等原因,可能會出現文字節點不包含文字,或者接連出現兩個文字節點的情況;當在某個節點上呼叫該方法時,就會在該節點的後代節點中查詢上述兩種情況;如果找到了空文字節點,則刪除它;如果找到相鄰的文字節點,則將它們合併為一個文字節點;

hasChildNodes()方法:

判斷節點是否有子節點,會返回一個布林值;

var mydiv = document.getElementById("mydiv");console.log(mydiv.childNodes.length);console.log(mydiv.hasChildNodes());if(mydiv.hasChildNodes())    console.log("有子節點");elseconsole.log("無");// 如果原始檔中有空白符,刪除的是空白文字節點if(mydiv.hasChildNodes())    mydiv.removeChild(mydiv.firstChild);// 全部刪除while(mydiv.hasChildNodes())    mydiv.removeChild(mydiv.firstChild);

總結:判斷一個節點是否有子節點,有三種方式;

node.firstChild !== null、node.childNodes.length > 0、node.hasChildNodes()方法;

contains()方法:

在實際開發中,經常需要知道某個節點是不是另一個節點的後代; 其接受一個後代節點,用於判斷是否為當前元素的後代節點,如果是,返回true,否則為false;

10
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 爬取冰冰B站千條評論,看看大家說了什麼