IE 在創(chuàng)建 DOM 樹時(shí)會忽略某些空白字符

字號:


    標(biāo)準(zhǔn)參考
    Node(節(jié)點(diǎn))不僅包括元素節(jié)點(diǎn),也包含文本節(jié)點(diǎn)、注釋節(jié)點(diǎn)、屬性節(jié)點(diǎn)等等,節(jié)點(diǎn)的類型可以使用 nodeType 來區(qū)分。
    在 HTML 源代碼中,位于標(biāo)簽之內(nèi)以及標(biāo)簽之間的文本(包括空白字符)將被創(chuàng)建為文本節(jié)點(diǎn)。
    關(guān)于 Node 的更多信息,請參考 DOM-1 Core Interface Node 及 DOM-2 Core Interface Node 中的內(nèi)容。
    關(guān)于 Text 的更多信息,請參考 DOM-2 Core Interface Text 中的內(nèi)容。
    問題描述
    IE 在創(chuàng)建 DOM 樹時(shí),會忽略某些空白字符,因此會比其他瀏覽器少創(chuàng)建一些文本節(jié)點(diǎn)。反過來說,同樣的一篇文檔,其他瀏覽器將比 IE 多創(chuàng)建一些文本節(jié)點(diǎn)。
    造成的影響
    用戶針對 IE 設(shè)計(jì)的腳本如果使用節(jié)點(diǎn)對象的 nodeList、firstChild、lastChild、previousSibling 或 nextSibling 方法,可能會因?yàn)榇藛栴}而無法在其他瀏覽器中達(dá)到相同的目的,如腳本執(zhí)行出錯(cuò),或?qū)﹀e(cuò)誤的目標(biāo)對象進(jìn)行了操作。
    受影響的瀏覽器
    IE6 IE7 IE8
    問題分析
    分析以下代碼:
    ...
    <!--測試元素-->
    <div id="a"> <div>div</div> <span id="b">span</span> <span>span</span> </div>
    <!--腳本輸出-->
    <pre>
    <script>
    //獲取父元素。
    var $a=document.getElementById("a");
    //測試 childNodes。
    var nodeList=$a.childNodes;
    var string="";
    for(var i=0;i<nodeList.length;i++)string+=nodeList[i].nodeType;
    document.writeln("nodeList: "+string);
    //測試 firstChild。
    document.writeln("firstChild: "+$a.firstChild.nodeType);
    //測試 lastChild。
    document.writeln("lastChild: "+$a.lastChild.nodeType);
    //獲取子元素。
    var $b=document.getElementById("b");
    //測試 previousSibling。
    document.writeln("previousSibling: "+$b.previousSibling.nodeType);
    //測試 nextSibling。
    document.writeln("nextSibling: "+$b.nextSibling.nodeType);
    //顯示 innerHTML。
    alert("|"+$a.innerHTML+"|");
    </script>
    </pre>
    ...
    注意以上代碼,外層 DIV 標(biāo)簽內(nèi)的各標(biāo)簽間有空格符??崭穹粯?biāo)記為紅色。
    根據(jù)規(guī)范中的描述,腳本的預(yù)計(jì)輸出情況如下:
    第一行輸出應(yīng)該是“nodeList: 3131313”,因?yàn)樵撛貎?nèi)的節(jié)點(diǎn)共有 7 個(gè):3 個(gè)元素節(jié)點(diǎn)穿插在 4 個(gè)文本節(jié)點(diǎn)之間。
    第二行輸出應(yīng)該是“firstChild: 3”,第一個(gè)節(jié)點(diǎn)是文本節(jié)點(diǎn)。
    第三行輸出應(yīng)該是“l(fā)astChild: 3”,最后一個(gè)節(jié)點(diǎn)也是文本節(jié)點(diǎn)。
    第四行輸出應(yīng)該是“previousSibling: 3”。本次的目標(biāo)元素(SPAN[id=b])的前一個(gè)節(jié)點(diǎn)是文本節(jié)點(diǎn)。
    第五行輸出應(yīng)該是“nextSibling: 3”,原因同上。
    這段代碼在不同的瀏覽器環(huán)境中的表現(xiàn):
    IE其他瀏覽器
    nodeList:113133131313
    firstChild:13
    lastChild:33
    previousSibling:13
    nextSibling:33
    最后彈出 DIV[id=a] 元素的 innerHTML 為:
    IE
    |<DIV>div</DIV><SPAN id=b>span</SPAN> <SPAN>span</SPAN> |
    其他瀏覽器:
    | <div>div</div> <span id="b">span</span> <span>span</span> |
    對原代碼中的“測試元素部分”進(jìn)行改動后(將其中第二個(gè) SPAN 更換為 DIV 元素):
    ...
    <!--測試元素-->
    <div id="a"> <div>div</div> <span id="b">span</span> <div>div</div> </div>
    ...
    再次測試,各瀏覽器表現(xiàn)如下:
    IE6 IE7 IE8Firefox Chrome Safari Opera
    nodeList:11313131313
    firstChild:13
    lastChild:13
    previousSibling:13
    nextSibling:33
    最后彈出 DIV[id=a] 元素的 innerHTML 為:
    IE
    |<DIV>div</DIV><SPAN id=b>span</SPAN>
    <DIV>div</DIV>|
    其他瀏覽器:
    | <div>div</div> <span id="b">span</span> <div>div</div> |
    可見:IE 在生成 DOM 樹時(shí),忽略了一些空白字符,從而比其他瀏覽器少創(chuàng)建了一些文本節(jié)點(diǎn)。這導(dǎo)致在使用 nodeList、firstChild、lastChild、previousSibling 或 nextSibling 方法時(shí),在 IE 和其他瀏覽器中得到的結(jié)果不一致。
    解決方案
    1. 沒有必要時(shí)盡量去掉各標(biāo)簽之間的空白字符。
    因?yàn)轫撁婺_本多是對“元素節(jié)點(diǎn)”進(jìn)行操作,因此只要保證各元素之間沒有文本節(jié)點(diǎn)(即源代碼中的標(biāo)簽之間沒有空白字符——包括空格符、換行符、制表符),就能使上述各屬性在各瀏覽器中的行為一致。如:
    <div id="a"><div>div</div><span id="b">span</span><span>span</span></div>
    另外,使用腳本創(chuàng)建并順次添加的元素,他們本身就是緊密相聯(lián)的,各元素之間并沒有文本節(jié)點(diǎn),因此這種情況也不必?fù)?dān)心上述兼容性問題,如:
    ...
    var $a=document.createElement("div");
    ...
    var $b=document.createElement("div");
    ...
    document.body.appendChild($a);
    document.body.appendChild($b);
    ...
    $a.nextSibling.className="foo";
    ...
    上述代碼中,'$a.nextSibling' 在所有瀏覽器中都將是 $b。
    2. 在獲取節(jié)點(diǎn)時(shí)做類型判斷。
    無法保證各元素之間沒有文本節(jié)點(diǎn)時(shí),則需要在針對節(jié)點(diǎn)的操作上添加類型判斷,如:
    function getPreviousElementSibling ($target) {
    var $previousNode = $target.previousSibling;
    while ($previousNode && $previousNode.nodeType!=1) {
    $previousNode = $previousNode.previousSibling;
    }
    return $previousNode;
    }
    另外,在非IE中,還可以使用 Element Traversal Specification 草案中提到的 previousElementSibling 和 nextElementSibling 獲取元素節(jié)點(diǎn),例如:以 Element.nextElementSibling 取得與元素 Element 的相鄰的下一個(gè)元素節(jié)點(diǎn)。