Element類型用於表現XML或HTML元素,提供對元素標簽名,子節點及特性的訪問。原型鏈的繼承關係為 某節點元素.__proto__->(HTML某元素Element.prototype)->HTMLElement.prototype->Element.prototype->Node.proto ...
Element類型用於表現XML或HTML元素,提供對元素標簽名,子節點及特性的訪問。原型鏈的繼承關係為 某節點元素.__proto__->(HTML某元素Element.prototype)->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype。
以HTML元素為例:document.documentElement.__proto__->HTMLHtmlElement.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype
Element節點實例有以下特征:以下特征均繼承自Node.prototype
- nodeType值為1
- nodeName值為元素標簽名
- nodeValue值為null
- parentNode可能是Document或Element
- 其子節點可能是Element,Text,Comment,ProcessingInstruction,CDATASection,EntityReference
要訪問元素標簽名,可以用nodeName(繼承自Node.prototype)屬性也可用tagName(繼承自Element.prototype)屬性,這兩個屬性會返回相同的值。但註意返回的字元串是大寫。在HTML中標簽名始終以全部大寫表示,而在XML中(有時也包括XHTML)標簽名則始終會與源碼中的保持一致。假如你不確定自己的腳本將會在HTML還是XML中執行,最好在比較之前進行大小寫轉化。
document.documentElement.tagName;// "HTML" document.documentElement.nodeName;// "HTML" document.documentElement.nodeName.toLowerCase();// "html"
目錄
- HTML元素
- 取得特性
- 設置特性
- attributes屬性
- 創建元素
- 元素的子節點
HTML元素
HTML元素的五種標準特性(ele.attributes[index或'屬性']或ele.getAttributeNode('屬性')得到特性節點),可以取得或修改。
(1).id:繼承自Element.prototype,元素在文檔中唯一的標識符。 document.body.id;// "Posts"
(2).className:繼承自Element.prototype,與元素的class特性對應,即為元素指定的css類。沒有將這個屬性命名為class,是因為class是ECMAScript的保留字。 document.forms[0].getElementsByTagName('div')[0].className;// "aspNetHidden"
(3).title:繼承自HTMLElement.prototype。有關元素的附加說明信息,一般通過工具提示條顯示出來。
(4).lang:繼承自HTMLElement.prototype。元素內容的語言代碼, document.documentElement.lang;// "zh-cn"
(5).dir:繼承自HTMLElement.prototype。語言的方向,值為"ltr"(從左至右)或"rtl"(從右至左)。是規定語言內容的文本方向不是文字順序顛倒。註意一點,應用dir="rtl"後雖然對文本整體是方向性的改變,但對標點符號和文本整體卻做了顛倒。其實很好理解,這個屬性是規定語言的方向,從右向左讀,句號肯定在讀的順序的最後也就是左邊。在換行的時候還是從截斷的文本整體偏向右側。
<div id="myID" class="bd" title="body" lang="en" dir="ltr">...</div>
並不是對所有屬性的修改都能直觀在頁面上表現出來。
對id或lang的修改對用戶而言是透明不可見的;
對title的修改只會在滑鼠移動到這個元素上時才顯示出來;
對dir的修改會在屬性被重寫的那一刻立即影響頁面中文本左右對齊方式;
修改className時,如果新類關聯了與此前不同的CSS樣式那麼就會立即應用該樣式;
關於瞭解所有HTML元素以及與之關聯的原型類型的構造器可參考高程三P263,有的元素是直接繼承自HTMLElement.prototype比如b元素,有的是繼承自HTML某元素Element.prototype,比如a元素,它的原型屬性指向HTMLAnchorElement.prototype。
(6).attributes:繼承自Element.prototype。返回一個NamedNodeMap的實例對象。
這裡擴展瞭解一下NamedNodeMap介面,原型鏈繼承關係為:ele.attributes.__proto__->NamedNodeMap.prototype->Object.prototype。NamedNodeMap介面表示屬性節點對象的集合,儘管NamedNodeMap裡面的對象可以像數組一樣通過索引進行訪問但它和NodeList不一樣,對象的順序沒有指定。NamedNodeMap集合是即時更新的,因此如果它內部包含的對象發生改變的話,該對象會自動更新到最新狀態。
- length:只讀,返回映射(map)中對象的數量。
- getNamedItem(str):返回一個給定名字對應的屬性節點(Attr)
- setNamedItem(attr):替換或添加一個屬性節點到映射map中,會直接反映到DOM中
- removeNamedItem(str):移除一個屬性節點,也會即時反映到文檔的DOM樹中
- item(idx):返回指定索引處的屬性節點,當索引超出範圍返回null
- getNamedItemNS():根據給定命名空間的參數和name返回一個attr對象
- setNamedItemNS():替換,添加給定命名空間參數和name參數的attr對象
- removeNamedItemNS():移除給定命名空間參數和name參數的attr對象
取得特性
(1).每個元素都有一個或多個特性,這些特性的用途是給相應元素或其內容附加信息。元素繼承自Element.prototype上的三個屬性,它們的功能是操作特性(不是屬性)的方法:
- setAttribute('attr','value')
- getAttribute('attr')
- removeAttribute('attr')
這三個方法都可操作自定義特性,但只有公認的特性才能被應用以屬性的形式添加到DOM對象中,以屬性方式操作這些特性會被同步到html標簽中。HTMLElement的5個特性都有相應屬性(意思是Element.prototype或HTMLElement.prototype上的屬性可直接通過.形式訪問)與其對應:id,title,lang,dir,className。在DOM中以屬性方式操作這幾個特性會同步到html標簽中。因為class特性是這5種特性之一,可以通過className屬性訪問,xsf特性不在Element.prototype或HTMLElement.prototype對象中有對應屬性所以通過屬性訪問方式獲取的值為undefiend。要想訪問xsf特性值可以通過getAttribute('xsf')(推薦)或getAttributeNode('xsf').value或attributes["xsf"].value訪問。
(2).當然元素還能通過繼承HTML某元素Element.prototype上的一些屬性,比如input元素的HTMLInputElement.prototype上的disabled可以通過inputele.disabled取得或設置值。inputele.disabled;// false表示該元素未被設置disabled屬性即未被禁用,inputele.disabled=true;// 表示為該元素設置不可用屬性。
(3).HTML5規範對自定義特性做了增強,只要自定義特性以'data-attrName'形式寫到html標簽中(setAttribute或直接html代碼存在均可),在DOM屬性中就可通過ele.dataset.attrName形式訪問自定義特性。
dataset屬性繼自HTMLElement.prototype,它的值是DOMStringMap的實例集合,原型鏈繼承關係為:ele.dataset.__proto__->DOMStringMap.prototype->Object.prototype。
(4).特性名是不區分大小寫的,getAttribute('id')和getAttribute('ID')都代表同一個特性。
介紹兩個特殊的特性:它們雖然有對應的屬性名,但屬性的值與通過getAttribute()返回的值並不相同。style屬性繼承自HTMLElement.prototype,on事件處理屬性繼承自HTMLElement.prototype或Element.prototype。
(a).style:用於通過css為元素指定樣式。
通過getAttribute()訪問返回的style特性值(在標簽中定義的)中包含CSS文本
通過style屬性訪問返回一個CSSStyleDeclaration類型集合對象,由於style屬性是用於以編程方式訪問訪問元素樣式的因此並沒有直接映射到style特性。該css屬性來自標簽中被設置的style特性。
並沒有background特性值,可以看到不論通過哪個方式獲得的結果都只有元素上style特性設置的屬性才會出現。
通過style屬性返回了一個CSSStyleDeclaration類型實例集合,原型鏈繼承關係為:div.style.__proto__->CSSStyleDeclaration.prototype->Object.prototype
獲得的集合中的屬性只有已設置的才有值,其餘的屬性為空字元串即使它們都有預設值。
簡單學習下CSSStyleDeclaration介面:代表css鍵值對的集合,它在一些API中被使用
- HTMLele.style 用於操作單個元素的樣式(<ele style="...">)
- 在getComputedStyle中應用:CSSStyleDeclaration是window.getComputedStyle()返回的只讀介面,其中每個鍵都有值,或被設置的值或預設的值。
ele.style.cssText:聲明塊的文本內容,修改這個屬性會直接將標簽中的style特性內容改變。
ele.style.length:屬性的數量即有具體值的css聲明的數量。window.getComputedStyle()返回值為262。
ele.style.parentRule:包含的CSSRule;
ele.style.getPropertyPriority('attr'):返回可選的優先順序
ele.style.getPropertyValue('attr'):返回屬性值
ele.style.item(idx):返回屬性名,有具體的值的返回屬性名,沒有具體值的返回""
ele.style.removeProperty():刪除的屬性,會直接反映到HTML文檔中元素style特性節點。返回""
ele.style.setProperty('attr','value','priority'):設置屬性,eg: document.body.style.setProperty('color','red','important')
(b).on事件屬性:以onclick為例,在元素上使用時,onclick特性中包含的是JavaScript代碼,但通過getAttribute()訪問返回相應代碼的字元串。在訪問onclick屬性時會返回一個JavaScript函數(當onclick屬性上不存在函數對象且未在元素標簽中指定相應特性(為什麼說不是屬性是因為如果在ele上沒有找到onclick屬性那就去標簽中找onclick特性值)返回null)。由於存在這些差異,在通過JavaScript以編程方式操作DOM時建議使用onclick屬性值,只有在取得自定義特性值的情況下才通過getAttribute()訪問。
當一個元素標簽中既有onclick特性,同時給ele.onclick指定函數(這操作並不會影響原來標簽中onclick特性的值),則最後只執行ele.onclick屬性的函數。
並且通過getAttribute訪問仍得到的是標簽上的特性值,且即使之前已經給onclick屬性賦值了但控制台顯示元素自身並沒有這個屬性。我不明白為什麼document.body自身上會沒有onclick屬性,那當訪問document.body.onclick時候去哪訪問onclick的值,按著原型鏈嗎?如果函數是在原型鏈上的onclick屬性上也不應該啊HTMLElement.prototype.onclick=function(){console.log(2)}這樣不就使這個方法成共用的了任何HTML元素實例都能訪問,這顯然不實際因為我們只想為某一元素設置某事件函數。對於document.body自身上會沒有onclick屬性不知道是不是JS引擎內部實現的,如果是那具體是怎麼實現的?知道的盆友麻煩告知~
document.body.getAttribute('onclick');// "(function(){console.log(1)})()" document.body.onclick;// function (){ console.log(2) } document.body.hasOwnProperty('onclick');// false
(c).在<=IE7通過getAttribute()方法訪問style特性和onclick這樣的事件處理程式特性時,返回的值與屬性的值相同。即getAttribute('style')返回的不是字元串而是對象,getAttribute('onclick')返回的不是字元串而是函數。雖然IE8已修複該bug但不同版本的不一致性還是建議使用屬性訪問HTML特性。
一道面試題:下列關於IE,FF下麵腳本的區別描述錯誤的是:感覺這道怪怪的考的是早期支持情況??
A.innerText IE支持,FF不支持 FF早期不支持
B.document.createElement FF支持,IE不支持 X
C.setAttribute('class','styleclass')FF支持,IE不支持 IE早期不支持
D.用setAttribute設置事件FF不支持,IE支持 X
IE:all支持innerText >IE8支持setAttribute設置特性或事件
FF:新版本支持,舊版本不支持outerHTML outerText innerText;setAttribute支持
設置特性
setAttribute('attr','value'):繼承自Element.prototype。參數為要設置的特性名和值,如果特性已經存在,setAttribute()會以指定值替換現有值,如果特性不存在,setAttribute會創建該屬性並設置相應值。
通過該方法可以操作HTML特性也可以操作自定義特性,通過這個方法設置的特性名會被統一轉換為小寫形式即"ID"轉換為"id"。
因為所有特性都是屬性,所以直接給屬性賦值就可以設置特性的值,但通過添加自定義屬性並不會成為該元素的特性這樣實際上為元素自身添加了屬性。
document.body.id="test"; document.body.getAttribute('id');// "test" document.body.hasOwnProperty('id');//false document.body.myid="test"; document.body.getAttribute("myid");// null document.body.hasOwnProperty('myid');//true
註:<=IE7中,setAtttribute()存在一些異常行為不但通過setAttribute()設置元素基本特性或事件特性沒用而且通過點賦值法設置元素屬性也不會反應到元素標簽中。儘管到IE8才解決這個bug,但還是推薦用點賦值法設置特性。
removeAttribute():繼承自Element.prototype,可以徹底刪除元素特性,不僅會清除特性值還會從元素中完全刪除特性。該方法不常用,但在序列化DOM元素時,可以通過它來確切指定要包含哪些特性。
attributes屬性
Element類型是使用attributes屬性的唯一一個DOM節點類型,attributes屬性繼承自Element.prototype。它的值是NamedNodeMap類型實例,也是個動態集合,元素的每一個特性都由一個Attr類型節點表示,每個節點都保存在NamedNodeMap對象中。
Object.getOwnPropertyNames(NamedNodeMap.prototype);// ["length", "item", "getNamedItem", "getNamedItemNS", "setNamedItem", "setNamedItemNS", "removeNamedItem", "removeNamedItemNS", "constructor"]
(1).getNamedItem(name):返回nodeName屬性等於name的特性節點。
document.body.attributes.getNamedItem("id");// id="test" document.body.attributes[0].nodeName;// "id"
(2).removeNamedItem(name):從列表中移除nodeName等於name的節點。該方法與在元素上調用removeAttribute()方法效果相同,直接刪除具有給定名稱的特性節點。這兩種方法唯一的區別就是返回值,removeNamedItem返回被刪除特性的Attr節點。
attrMap.removeNamedItem('style');// style="overflow: hidden;" document.body.removeAttribute('class');// undefined
(3).setNamedItem(attrnode):向列表中添加屬性節點,以節點的nodeName屬性為索引。
var attr=document.createAttribute('class'); attr.value="as"; attr;// class="as" document.body.attributes.setNamedItem(attr); document.body.attributes;//NamedNodeMap {0: id, 1: style, 2: aria-hidden, 3: class, length: 4}
(4).item(pos):返回位於數字pos位置處的特性節點。
document.body.attributes.item(3);// class="as"
attributes屬性中包含一系列節點,如果想要遍歷元素特性attributes是個很好的選擇。每個節點的nodeName值就是特性節點的名稱,節點的nodeValue值就是特性的值。
document.body.attributes.getNamedItem("id").nodeValue;// "test" document.body.attributes["id"].nodeValue;// "test"
設置特性的值:先取得特性節點然後將其nodeValue設置為新值。
當你設置document.body.id="test"時候JS引擎內部可能完成瞭如下操作
document.body.id="newid"; transAttr(document.body,'id'); function transAttr(ele,id){ var attrMap=ele.attributes; for(var i in attrMap){ if(attrMap.hasOwnProperty(i)){ if(attrMap[i].nodeName=='id'){ attrMap[i].nodeValue=ele.id; } } } delete document.body.id; }
在需要將DOM結構序列化為XML或HTML字元串時,多數都會涉及遍歷元素特性。以下代碼展示瞭如何迭代元素的每一個特性然後將它們構造成name="value"這樣的鍵值對形式
function outputAttributes(ele){ var pairs=new Array(), attrName,attrValue,i,len; for(i=0;i<ele.attributes.length;i++){ attrName=ele.attributes[i].nodeName; attrValue=ele.attributes[i].nodeValue; pairs.push(attrName+"=\""+attrValue+"\""); } return pairs.join(" "); }
outputAttributes(document.body);// "id="cd" aria-hidden="true""
這個函數使用了一個數組來保存名值對,最後再以空格分隔符將它們拼接起來(這是序列化長字元串時的常用技巧)。
- 針對attributes中的特性,不同瀏覽器返回順序不同,這些特性在XML或HTML代碼中出現的先後順序不一定與它們出現在attributes對象中的順序一致。
- <=IE7會返回HTML元素所有可能特性,包括沒有指定的特性。針對IE7這一bug可以改進一下程式,每個特性節點都有一個specified(繼承自Attr.prototype)的屬性,specified=true表明要麼是在HTML元素中指定了相應特性要麼通過setAttribute()方法設置了該特性。在<=IE7中不論有沒有設置過某特性,某特性都有specified值,但被設置過的值為true,所有未設置過的特性該屬性都為false。在其他瀏覽器中不會為這類特性生成對應的特性節點(因此在這些瀏覽器中任何特性節點的specified值始終為true)
當並沒有為document.body設置特性節點class //<=IE7 document.body.attributes["class"].specified;// false //IE10 document.body.attributes["class"];// undefiend
改進後的代碼為:
function outputAttributes(ele){ var pairs=new Array(), attrName,attrValue,i,len; for(i=0,len=ele.attributes.length;i<len&&ele.attributes[i].specified==true;i++){ attrName=ele.attributes[i].nodeName; attrValue=ele.attributes[i].nodeValue; pairs.push(attrName+"=\""+attrValue+"\"") } return pairs.join(" "); }
創建元素
document.createElement():繼承自Document.prototype,參數為標簽名,這個標簽名在HTML文檔中不區分大小,在XML(包括XHTML)文檔中是區分大小寫的。在使用document.createElement創建新元素的同時,也為新元素設置了ownerDocument(繼承自Node.protoype)屬性,此時還可操作元素的特性為它添加更多子節點以及執行其他操作。
var div=document.createElement('div'); div.id="myNewid";// "myNewid" div.className="box";// "box"
在新元素上設置這些特性只是給它們賦予了相應信息,由於新元素尚未被添加到文檔樹中,因此設置這些特性不會影響瀏覽器顯示。要把新元素添加到文檔樹中,可使用appendChild(),insertBefore(),replaceChild()均繼承自Node.prototype。一旦將元素添加到文檔樹,瀏覽器就會立即呈現該元素。此後對這個元素所做的任何修改都會實時反映在瀏覽器中。
在<=IE8中以另一種方式使用createElement,即為這個方法傳入完整的元素標簽也可以包含屬性,document.createElement('<div id="test"></div>') 。這種方式有助於避開在IE7及更早版本中動態創建元素(document.createElement('div')然後插入叫動態創建)的某些問題,之前存在以下這些問題:
- 不能設置動態創建的<iframe>元素的name特性
- 不能通過表單的reset()方法重設動態創建的<input>元素。
- 動態創建的type特性值為"reset"的<button>元素重設不了表單。
- 動態創建的一批name相同的單選按鈕彼此毫無關係,name值相同的一組單選按鈕本來應該用於表示同一選項的不同值,但動態創建的一批這種單選按鈕之間卻沒有這種關係。
上述所有問題都可通過在createElement()中指定完整的HTML標簽來解決。
if(window.navigator.userAgent.search(/MSIE([^;]+)/)){ //創建一個帶name特性的iframe標簽 var iframe=document.createElement('<iframe name="myframe"></iframe>'); //創建input元素 var input=document.createElement('<input type="checkbox">'); //創建button元素 var button=document.createElement('<button type="reset"></button>') ; //創建一個單選按鈕 var radio1=document.createElement('<input type="radio" name="choice" value="one">'); var radio2=document.createElement('<inpur type="radio" name="choice" value="two">'); }
元素的子節點
元素的childNodes屬性(繼承自Node.prototype)包含了它所有子節點,這些子節點可能是元素,文本節點,註釋,處理指令。不同瀏覽器在看待這些節點方面存在不同。
<ul id="myList"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul>
IE:
IE9~11
IE5~8
Chrome46.0.2490.80:
FF44.0.2 :
如果需要通過childNodes屬性遍歷子節點,通常要先檢查一下當前節點的nodeType屬性。
var ul=document.getElementById('myList'); for(var i=0;i<ul.childNodes.length;i++){ if(ul.childNodes[i].nodeType==1){ //do else } }
如果想通過標簽名取得子節點或後代節點,元素也支持getElementsByTagName()(繼承自Element.prototype),返回HTMLCollection類型實例集合是返回當前元素的後代(如果有多層嵌套的話包括子元素和子元素的後代)。document.getElementsByTagName()是繼承自Document.prototype。
參考
《JavaScript高級程式設計》
反本求源——DOM元素的特性與屬性
MDN NamedNodeMap
MDN CSSStyleDeclaration