本資源引自: SD9006: IE 混淆了 DOM 對象屬性(property)及 HTML 標簽屬性(attribute), 造成了對 setAttribute、getAttribute 的不正確實現(xiàn) - W3Help http://www./zh-cn/causes/SD9006 -------------------------------------------------------------------------------------------------------------------------- SD9006: IE 混淆了 DOM 對象屬性(property)及 HTML 標簽屬性(attribute),造成了對 setAttribute、getAttribute 的不正確實現(xiàn)作者:陸遠
標準參考根據(jù) DOM (Core) Level 1 規(guī)范中的描述,getAttribute 與 setAttribute 為 Element 接口下的方法,其功能分別是通過 "name" 獲取和設置一個元素的屬性(attribute)值。getAttribute 方法會以字符串的形式返回屬性值,若該屬性沒有設定值或缺省值則返回空字符串。setAttribute 方法則無返回值。 DOMString getAttribute(in DOMString name); void setAttribute(in DOMString name, in DOMString value) raises(DOMException); HTML 文檔中的 DOM 對象的屬性(property)被定義在 DOM (HTML) 規(guī)范中。這個規(guī)范中明確定義了 document 對象以及所有標準的 HTML 元素所對應的 DOM 對象擁有的屬性及方法。 舉例來說,在一個 HTML 文檔中存在一個 SPAN 元素,根據(jù) DOM (HTML) 規(guī)范,SPAN 元素在頁面中生成一個相對應的對象,這個對象派生自 HTMLElement 接口,而 HTMLElement 接口則繼承自 Element 接口。HTMLElement 接口中包含 id 屬性。我們可以通過 HTMLElement 接口中的 id 屬性獲得這個元素的標識符,而通過 Element 接口中的 getAttibute 方法傳入?yún)?shù) "id" 同樣可以獲得標識符。 關于 getAttribute 與 setAttribute 的詳細信息,請參考 DOM (Core) Level 1 及 Level 2 中的內容。 關于 HTML DOM 對象 的詳細信息,請參考 DOM (HTML) Document Object Model HTML,特別是 1.6.1. Property Attributes 中的內容。 問題描述IE6 IE7 IE8(Q) 混淆了 DOM 對象的屬性(property)及 HTML 標簽屬性(attribute)這兩個概念。其對于 getAttribute 及 setAttribute 方法的實現(xiàn)與 HTML DOM 對象的屬性的 getter 與 setter 操作等價,這個錯誤的實現(xiàn)方式導致了一系列的兼容性問題。而在 IE8(S) 中,導致的大多數(shù)兼容性問題已不存在,但是仍然可以通過 "Element.PropertyName" 訪問到這個 HTML 元素的自定義屬性。 造成的影響
受影響的瀏覽器
問題分析在 MSDN 中的一篇名為 "Attribute Differences in Internet Explorer 8" 的一篇官方文檔中提到,由于 DOM 屬性(文中稱作 "DOM attribute" )通常與其所對應的 HTML 屬性(文中稱作 "content attribute" )同名,因此常常被認為這兩個 "屬性" 的值是相同的。例如 DOM 中 BODY 對象的 background 屬性(property)看上去似乎與 HTML 文檔中 BODY 元素的 background 屬性(attribute)的值相同。在早期版本的 IE 瀏覽器中, "property" 的值與分配給元素 "attribute" 的值相同,而同時 "property" 的值也以元素 "attribute" 的值為基礎,因此,這兩個值之間在術語上已變得含糊不清。 對于絕大多數(shù) "property" 與 "attribute" 在名稱及值類型上是統(tǒng)一的。但也有一些特例,
接下來通過代碼分析 IE 由于混淆 DOM 對象的屬性(property)及 HTML 屬性(attribute)導致的常見的兼容性問題。 1. IE 各版本中可以通過 "Element.PropertyName" 訪問 HTML 元素的自定義屬性代碼 custom_attributes_and_properties.html: <!DOCTYPE html> <html> <head> <script> function $(id) { return document.getElementById(id); } window.onload = function () { var d1 = $("d1"); d1.setAttribute("setAttr2", "value2"); d1.customProperty3 = "value3"; d1.id = "d2"; var dn = document.createElement("div"); var str = []; $("info2").value = $("cont").innerHTML; for (var i in d1) { if (!(i in dn)) { str.push(i + ":" + d1[i] + "<br />"); } } $("info1").innerHTML = (str.length == 0) ? "N/A" : str.join(""); } </script> </head> <body style="font:20px Arial;"> <div id="cont"><div id="d1" customAttr1="value1">'d1': HTMLDivElement 1</div></div> <br /> <h1>Extra property:</h1> <div id="info1"></div> <br /> <h1>HTML code:</h1> <textarea id="info2" cols="200" style="font:16px Arial;"></textarea> </body> </html> 上面代碼中,DIV 元素【d1】在其 HTML 標簽上出現(xiàn)了 1 個自定義屬性(attribute)——customAttr1。頁面加載完成后,使用 setAttribute 方法為【d1】的標簽上設置了 setAttr2 屬性,為【d1】對應的 HTMLDivElement 對象設置了 customProperty3 屬性,為【d1】對應的對象設置了 id 屬性。 這段代碼在不同的瀏覽器環(huán)境中的表現(xiàn):
在解析 HTML 文檔時,所有版本的 IE 中自定義的 HTML 標簽屬性成為了該元素對應的 DOM 對象中的屬性,這使得可以通過 "d1.customAttr1" 或者 "d1["customAttr1"]" 獲取的到這個自定義 HTML 屬性的值。在使用 setAttribute 方法為元素的標簽設置自定義 HTML 屬性后,該元素對應的 DOM 對象上也會自動綁定上這個屬性名及其屬性值。同時,在為元素對應的 DOM 對象設置了一個屬性后,其在 HTML 文檔中對應的元素的 HTML 標簽上也會出現(xiàn)這個自定義的屬性名及其屬性值。 根據(jù)規(guī)范,HTMLDivElement 接口繼承自 HTMLElement,實現(xiàn)了 HTMLDivElement 接口的對象(即 HTML 文檔中 DIV 元素所對應的 DOM 對象)擁有 HTMLElement 接口中定義的所有屬性(id、title、lang、dir、className 屬性)及方法,以及其私有的 align 屬性。【d1】最初在其 HTML 標簽代碼中有id 及 customAttr1 兩個屬性,根據(jù) DOM 規(guī)范,DIV 元素的 id 屬性是會將其暴露給其對應的 HTMLDivElement 對象的 id 屬性的,此時這兩個層面的 id 屬性會保持同步,即瀏覽器在解析 HTML 文檔時,會將【d1】標簽內的 id 屬性及其值綁定至【d1】所對應的 DOM 對象上;同時當設置其 DOM 對象的 id 屬性時,【d1】的 HTML 標簽上的 id 屬性也會隨著發(fā)生變化。 本例中,可以認為 IE 沿用了 W3C 規(guī)范中明確棄用的做法,將 HTML 規(guī)范中未定義的自定義屬性也“暴露”給了對應的 DOM 對象,使得 HTML 元素的自定義屬性與對應的 DOM 對象的自定義屬性也會像規(guī)定中定義了綁定關系的那些標準屬性一樣可以保持著一種同步關系。 2. IE 各版本中可以通過 "Element.getAttribute("value")" 及 "Element.setAttribute("value", "XXX")" 獲取和設置 INPUT 元素的實時輸入的值代碼 attribute_and_property_value.html: <!DOCTYPE html> <html> <head> <style> input, textarea { font:12px consolas; width:400px; } </style> <script> function $(id) { return document.getElementById(id); } function addInputEvent(elem, handler, useCapture) { elem.addEventListener ? elem.addEventListener("input", handler, false) : elem.attachEvent("onpropertychange", handler); } window.onload = function () { var psw = $("psw"), cont = $("cont"), ta = $("ta"), txt; var inputing = function () { txt = cont.innerHTML + '\npsw.value:' + psw.value + '\npsw.getAttribute("value"):' + psw.getAttribute("value"); ta.value = txt; } addInputEvent(psw, inputing); inputing(); } </script> </head> <body> <div id="cont"><input id="psw" type="text" value="old value" /></div> <textarea id="ta" style="height:100px;"></textarea> </body> </html> 上面代碼中 INPUT 文本框的 value 初始值為 "old value",當在文本框內輸入字符時,會在其下方實時顯示 INPUT 文本框的 HTML 標簽代碼,以及其 DOM 對象的 value 屬性值和 getAttribute("value") 得到的值。 這段代碼在不同的瀏覽器環(huán)境中的表現(xiàn):
根據(jù) DOM Level 2 HTML 規(guī)范中的描述,當 INPUT 元素 type 屬性為 "text"、"file" 或 "password" 時,其對應的 HTMLInputElement 對象的 value 屬性代表了這個控件 "當前值",修改這個屬性會改變控件的 "當前值",但是并不會改變其 HTML 標簽上的 value 屬性。 可以看到在 Firefox Chrome Safari Opera 中,在 INPUT 元素的輸入新的 "當前值" 盡可以改變文本框對應的 DOM 對象的 value 屬性。而在 IE 中,HTML 標簽的 value 屬性也會跟隨 "當前值" 的變化而變化。 所以,只有在 IE 中可以通過 getAttribute("value") 可以獲取到 INPUT 文本框內的實時內容。 3. IE6 IE7 IE8(Q) 中無法通過 "Element.setAttribute("class", "AttributeValue")" 設置元素的 class 屬性代碼 attribute_and_property_class.html: <!DOCTYPE html> <html> <head> <style> div { width:300px; height:100px; background:#eee; font:12px Arial; } .d1 { background:pink; font:18px Arial; } .d2 { background:gold; font:18px Arial; } .d3 { background:plum; font:18px Arial; } </style> <script> function getClass(obj) { obj.innerHTML = '.className=' + obj.className + '<br>.getAttribute("class")=' + obj.getAttribute("class") + '<br>.getAttribute("className")=' + obj.getAttribute("className"); } window.onload = function () { var d1 = document.getElementById("d1"); var d2 = document.getElementById("d2"); var d3 = document.getElementById("d3"); d1.className = "d1"; d2.setAttribute("class", "d2"); d3.setAttribute("className", "d3"); getClass(d1); getClass(d2); getClass(d3); } </script> </head> <body> <div id="d1">d1</div> <div id="d2">d2</div> <div id="d3">d3</div> </body> </html> 上面代碼中分別使用 obj.className = "XXX"、obj.setAttribute("class", "XXX")、obj.setAttribute("className", "XXX") 試圖為【d1】、【d2】、【d3】設置一個 CSS 的 class。然后對于這三個 DIV 元素再使用 obj.className、obj.getAttribute("class")、obj.getAttribute("className") 得到它們的返回值。 這段代碼在不同的瀏覽器環(huán)境中的表現(xiàn):
可以看到,在 IE8(S) Firefox Chrome Safari Opera 中,結果符合規(guī)范。而在 IE6 IE7 IE8(Q) 中,無法通過 setAttribute 和 getAttribute 方法通過傳入 class 參數(shù)做為屬性名來設置及獲取元素的 class 屬性,而必須通過傳入 className 參數(shù)。在其他瀏覽器中,傳入 className 參數(shù)僅僅是為元素的 HTML 標簽設置與獲取一個自定義的 className 屬性的值。 4. IE6 IE7 IE8(Q) 中無法通過 "Element.setAttribute("style", "AttributeValue")" 設置元素的 style 屬性代碼 attribute_and_property_style.html: <!DOCTYPE html> <html> <head> <style> * { font-family:Arial; } div { width:400px; height:100px; background:#eee; font-size:12px; margin-bottom:1px; } </style> <script> function getStyle(obj) { obj.innerHTML = '.style.cssText=' + (obj.style.cssText).toLowerCase() + '<br>.getAttribute("style")=' + ("" + obj.getAttribute("style")).toLowerCase(); } window.onload = function () { var d1 = document.getElementById("d1"); var d2 = document.getElementById("d2"); var styleText = "background-color:rgb(51, 204, 204); font-size:16px"; d1.style.cssText = styleText; d2.setAttribute("style", styleText); getStyle(d1); getStyle(d2); } </script> </head> <body> <div id="d1">d1</div> <div id="d2">d2</div> </body> </html> 上面代碼中分別使用 obj.style.cssText = "XXX"、obj.setAttribute("style", "XXX") 試圖為【d1】、【d2】設置一個內聯(lián)樣式。然后對于這兩個 DIV 元素再使用 obj.style.cssText、obj.getAttribute("style") 得到它們的返回值。 這段代碼在不同的瀏覽器環(huán)境中的表現(xiàn):
可以看到,在 IE8(S) Firefox Chrome Safari Opera 中,結果符合規(guī)范。而在 IE6 IE7 IE8(Q) 中,無法通過 setAttribute 和 getAttribute 方法通過傳入 style 參數(shù)做為屬性名來設置及獲取元素的 style 屬性中的內聯(lián)樣式,getAttribute("style") 返回的是一個 CSSStyleDeclaration 對象。 5. IE6 IE7 IE8(Q) 中無法通過諸如 "Element.setAttribute("onclick", "alert('ok')")" 為元素綁定事件代碼 attribute_and_property_event.html: <!DOCTYPE html> <html> <head> <style> * { font:12px Consolas; } button, textarea { width:700px; } </style> <script> function $(id) { return document.getElementById(id); } function getOnclick(index) { $("d" + index).value = $("cont" + index).innerHTML; } window.onload = function () { var b1 = $("b1"); var b2 = $("b2"); var cont1 = $("cont1"); var cont2 = $("cont2"); var s1 = $("s1"); var s2 = $("s2"); var funcStr1 = "$('s1').innerHTML='set string<br>get '+typeof this.getAttribute('onmouseover')"; var funcStr2 = "$('s2').innerHTML='set function<br>get '+typeof this.getAttribute('onmouseover')"; b1.setAttribute("onclick", funcStr1); b2.setAttribute("onclick", new Function(funcStr2)); b1.click(); b2.click(); } </script> </head> <body> <div id="cont1"><button id="b1" type="button" onmouseover="">"$('s1').innerHTML='trigger'"</button></div><span id="s1">N/A</span><br /> <br /><br /> <div id="cont2"><button id="b2" type="button" onmouseover="">function () { $('s2').innerHTML='trigger' }</button></div><span id="s2">N/A</span><br /> </body> </html> 上面代碼中分別使用 obj.setAttribute("onclick", "[code]"、obj.setAttribute("onclick", function () { [code] }) 試圖為【b1】、【b2】設置一個內聯(lián)事件。然后觸發(fā)這兩個 BUTTON 元素的 click 事件。 這段代碼在不同的瀏覽器環(huán)境中的表現(xiàn):
可以看到,在 IE8(S) Firefox Chrome Safari Opera 中,結果符合規(guī)范。而在 IE6 IE7 IE8(Q) 中,無法通過 setAttribute 方法傳入一段代碼字符串設置一個元素的內聯(lián)事件,而必須傳入一個 function 類型的對象;獲取一個已有的內聯(lián)事件的屬性值也是 function 類型,而不是規(guī)范中的字符串類型。 6. IE6 IE7 IE8(Q) 中可以通過諸如 "Element.getAttribute("offsetHeight")" 的方式獲得元素的一些 DOM 屬性的值,也可以通過諸如 "Element.setAttibute("innerHTML", "AttributeValue")" 的方式設置元素的一些非只讀 DOM 屬性的值代碼 attribute_and_property_DHTML.html: <!DOCTYPE html> <html> <head> <style> * { font-family:Arial; } div { width:100px; height:100px; background:#eee; font-size:12px; } textarea { width:600px; } </style> <script> function getStyle(obj) { obj.innerHTML = '.style.cssText=' + (obj.style.cssText).toLowerCase() + '<br>.getAttribute("style")=' + ("" + obj.getAttribute("style")).toLowerCase(); } window.onload = function () { var d = document.getElementById("d"); var cont= document.getElementById("cont"); var info= document.getElementById("info"); var s = 'd.getAttribute("offsetHeight"): '; s += d.getAttribute("offsetHeight") + "\n"; d.setAttribute("innerHTML", "other text"); info.value = s + cont.innerHTML; } </script> </head> <body> <div id="cont"><div id="d">text text text text text text text</div></div> <textarea id="info"></textarea> </body> </html>、 這段代碼在不同的瀏覽器環(huán)境中的表現(xiàn):
可以看到,在 IE8(S) Firefox Chrome Safari Opera 中,由于【d】的 HTML 標簽中沒有 "offsetHeight" 屬性,所以 getAttribute("offsetHeight") 根據(jù)規(guī)范要求返回了 null;setAttribute("innerHTML", "other text") 則為【d】的 HTML 標簽上設置了一個 innerHTML 屬性,值為 other text。 通過上述幾個測試樣例,可以看到 IE6 IE7 IE8(Q) 中,Element.getAttribute("attrName") 與 Element.attrName 等效,Element.setAttribute("attrName, "XXX") 與 Element.attrName = "XXX" 等效。IE8(S) 修復了由 getAttribute、setAttribute 方法所帶來的大多數(shù)兼容性問題,但并沒有改變 IE 本身對于 DOM 對象的屬性(property)及 HTML 標簽屬性(attribute)相互混淆的錯誤設計,對于表單元素的 value 屬性以及自定義 HTML 屬性仍然保持與 IE6 IE7 相同的現(xiàn)象。 結合這些測試結果,列表如下:
解決方案
參見知識庫相關問題測試環(huán)境
關鍵字getAttribute setAttribute attribute property DOM style className innerHTML binding event 本篇文章正在公測階段,錯誤在所難免,歡迎大家提出寶貴的意見和建議。 |
|