JavaScript操做DOM的那些坑

js在操做DOM中存在着許多跨瀏覽器方面的坑,本文花了我將近一週的時間整理,我將根據實例整理那些大大小小的「坑」。javascript

DOM的工做模式是:先加載文檔的靜態內容、再以動態方式對它們進行刷新,動態刷新不影響文檔的靜態內容。css

PS:IE 中的全部 DOM 對象都是以 COM 對象的形式實現的,這意味着 IE 中的 DOM可能會和其餘瀏覽器有必定的差別。html

Node 接口

特性/方法 類型/返回類型 說 明
nodeName String 節點的名字;根據節點的類型而定義
nodeValue String 節點的值;根據節點的類型而定義
nodeType Number 節點的類型常量值之一
ownerDocument Document 返回某元素的根元素
firstChild Node 指向在childNodes列表中的第一個節點
lastChild Node 指向在childNodes列表中的最後一個節點
childNodes NodeList 全部子節點的列表
previousSibling Node 返回選定節點的上一個同級節點,若不存在,則返回null
nextSibling Node 返回被選節點的下一個同級節點,若不存在,則返回null
hasChildNodes() Boolean 若是當前元素節點擁有子節點,返回true,不然返回false
attributes NamedNodeMap 返回包含被選節點屬性的 NamedNodeMap
appendChild(node) node 將node添加到childNodes的末尾
removeChild(node) node 從childNodes中刪除node
replaceChild(newnode, oldnode) Node 將childNodes中的oldnode替換成newnode
insertBefore Node 在已有子節點以前插入新的子節點

firstChild 至關於 childNodes[0]lastChild 至關於childNodes[box.childNodes.length - 1]java

nodeType返回結點的類型

--元素結點返回1
--屬性結點返回2
--文本結點返回3

innerHTML 和 nodeValue

對於文本節點,nodeValue 屬性包含文本。

對於屬性節點,nodeValue 屬性包含屬性值。

nodeValue 屬性對於文檔節點和元素節點是不可用的。

二者區別node

box.childNodes[0].nodeValue = '<strong>abc</strong>';//結果爲:<strong>abc</strong>
abcbox.innerHTML = '<strong>abc</strong>';//結果爲:abc

nodeName屬性得到結點名稱

--對於元素結點返回的是標記名稱,如:<a herf><a>返回的是"a"
--對於屬性結點返回的是屬性名稱,如:class="test" 返回的是test
--對於文本結點返回的是文本的內容

tagName

document.getElementByTagName(tagName):返回一個數組,包含對這些結點的引用

getElementsByTagName()方法將返回一個對象數組 HTMLCollection(NodeList),這個數組保存着全部相同元素名的節點列表。jquery

document.getElementsByTagName('*');//獲取全部元素

PS:IE 瀏覽器在使用通配符的時候,會把文檔最開始的 html 的規範聲明看成第一個元素節點。web

document.getElementsByTagName('li');//獲取全部 li 元素,返回數組
document.getElementsByTagName('li')[0];//獲取第一個 li 元素,HTMLLIElement
document.getElementsByTagName('li').item(0);//獲取第一個 li 元素,HTMLLIElement
document.getElementsByTagName('li').length;//獲取全部 li 元素的數目

節點的絕對引用:

返回文檔的根節點:document.documentElement
返回當前文檔中被擊活的標籤節點:document.activeElement
返回鼠標移出的源節點:event.fromElement
返回鼠標移入的源節點:event.toElement
返回激活事件的源節點:event.srcElement

節點的相對引用:(設當前對節點爲node)

返回父節點:node.parentNode || node.parentElement(IE)
返回子節點集合(包含文本節點及標籤節點):node.childNodes
返回子標籤節點集合:node.children
返回子文本節點集合:node.textNodes
返回第一個子節點:node.firstChild
返回最後一個子節點:node.lastChild
返回同屬下一個節點:node.nextSibling
返回同屬上一個節點:node.previousSibling

節點信息

是否包含某節點:node.contains()

是否有子節點node.hasChildNodes()

建立新節點

createDocumentFragment()--建立文檔碎片節點
createElement(tagname)--建立標籤名爲tagname的元素
createTextNode(text)--建立包含文本text的文本節點

獲取鼠標點擊事件的位置

document.onclick = mouseClick;

function mouseClick(ev){
    ev = ev || window.event;//window.event用來兼容IE
    var x = 0; var y = 0;

    if(ev.pageX){
        x = ev.pageX;
        y = ev.pageY;
    }else if(ev.clientX){
        var offsetX = 0 , offsetY = 0;
        if(document.documentElement.scrollLeft){
            offsetX = document.documentElement.scrollLeft;
            offsetY = document.documentElement.scrollTop;
        }else if(document.body){
            offsetX = document.body.scrollLeft;
            offsetY = document.body.scrollTop;
        }
        x = ev.clientX + offsetX;
        y = ev.clientY + offsetY;
    }
    alert("你點擊的位置是 x="+ x + " y=" + y);
}

如下所描述的屬性在chromeSafari 都很給力的支持了。chrome

問題一:FirefoxChromeSafariIE9都是經過非標準事件的pageXpageY屬性來獲取web頁面的鼠標位置的。pageX/Y獲取到的是觸發點相對文檔區域左上角距離,以頁面爲參考點,不隨滑動條移動而變化segmentfault

問題二:在IE 中,event 對象有 x, y 屬性(事件發生的位置的 x 座標和 y 座標)火狐中沒有。在火狐中,與event.x 等效的是 event.pageXevent.clientXevent.pageX 有微妙的差異(當整個頁面有滾動條的時候),不過大多數時候是等效的。 後端

offsetX:IE特有,chrome也支持。鼠標相比較於觸發事件的元素的位置,以元素盒子模型的內容區域的左上角爲參考點,若是有boder,可能出現負值

問題三:
scrollTop爲滾動條向下移動的距離,全部瀏覽器都支持document.documentElement

其他參照:http://segmentfault.com/a/119...

參照表

+爲支持,-爲不支持):

offsetX/offsetY:W3C- IE+ Firefox- Opera+ Safari+ chrome+

x/y:W3C- IE+ Firefox- Opera+ Safari+ chrome+

layerX/layerY:W3C- IE- Firefox+ Opera- Safari+ chrome+

pageX/pageY:W3C- IE- Firefox+ Opera+ Safari+ chrome+

clientX/clientY:W3C+ IE+ Firefox+ Opera+ Safari+ chrome+

screenX/screenY:W3C+ IE+ Firefox+ Opera+ Safari+ chrome+

查看下方DEMO
你會發現offsetXFirefox下是undefined,在chromeIE則會正常顯示。

https://jsfiddle.net/f4am208m...

圖片描述

offsetLeft和style.left區別

1.style.left返回的是字符串,好比10px。而offsetLeft返回的是數值,好比數值10

2.style.left是可讀寫的,offsetLeft是隻讀的

3.style.left的值須要事先定義(在樣式表中定義無效,只能取到在html中定義的值),不然取到的值是空的

getComputedStyle與currentStyle

getComputedStyle()接受兩個參數:要取得計算樣式的元素和一個僞元素,若是不須要僞元素,則能夠是null。然而,在IE中,並不支持getComputedStyle,IE提供了currentStyle屬性。

getComputedStyle(obj , false ) 是支持 w3c (FF十二、chrome 1四、safari):在FF新版本中只須要第一個參數,即操做對象,第二個參數寫「false」也是你們通用的寫法,目的是爲了兼容老版本的火狐瀏覽器。
缺點:在標準瀏覽器中正常,但在IE6/7/8中不支持

window.onload=function(){
    var oBtn=document.getElementById('btn');
    var oDiv=document.getElementById('div1');

    oBtn.onclick=function(){
        //alert(oDiv.style.width); //寫在樣式表裏沒法讀取,只能獲得寫在行內的
        //alert(getComputedStyle(oDiv).width); //適用於標準瀏覽器       IE六、七、8不識別
        //alert(oDiv.currentStyle.width); //適用於IE瀏覽器,標準瀏覽器不識別
        if(oDiv.currentStyle){
            alert(oDiv.currentStyle.width);
        }else{
            alert(getComputedStyle(oDiv).width);
        }

    };
};

取消表單提交

<script type="text/javascript">
    function listenEvent(eventObj,event,eventHandler){
        if(eventObj.addEventListener){
            eventObj.addEventListener(event,eventHandler,false);
        }else if(eventObj.attachEvent){
            event = "on" + event;
            eventObj.attachEvent(event,eventHandler);
        }else{
            eventObj["on" + event] = eventHandler;
        }
    }

    function cancelEvent(event){
        if(event.preventDefault){
            event.preventDefault();//w3c
        }else{
            event.returnValue = true;//IE
        }
    }

    window.onload = function () {
        var form = document.forms["picker"];
        listenEvent(form,"submit",validateFields);
    };

    function validateFields(evt){
        evt = evt ? evt : window.event;
        ...
        if(invalid){
            cancelEvent(evt);
        }
    }
</script>

肯定瀏覽器窗口的尺寸

對於主流瀏覽器來講,好比IE9FirefoxChromeSafari,支持名爲innerWidthinnerHeight的窗口對象屬性,它返回窗口的視口區域,減去任何滾動條的大小。IE不支持innerWidthinnerHeight

<script type="text/javascript">
    function size(){
        var w = 0, h=0;

        if(!window.innerWidth){
            w = (document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth);

            h = (document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight);
        }else{
            w = window.innerWidth;
            h = window.innerHeight;
        }
        return {width:w,height:h};
    }

    console.log(size());//Object { width: 1366, height: 633 }
</script>

實用的 JavaScript 方案(涵蓋全部瀏覽器):

var w=window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;

var h=window.innerHeight || document.documentElement.clientHeight|| document.body.clientHeight;

對於 IE 六、七、8的方案以下:

document.documentElement.clientHeight
document.documentElement.clientWidth

或者

document.body.clientHeight
document.body.clientWidth

Document對象的body屬性對應HTML文檔的<body>標籤。Document對象的documentElement屬性則表示 HTML文檔的根節點。

attributes 屬性

attributes 屬性返回該節點的屬性節點集合。

document.getElementById('box').attributes//NamedNodeMap
document.getElementById('box').attributes.length;//返回屬性節點個數
document.getElementById('box').attributes[0]; //Attr,返回最後一個屬性節點
document.getElementById('box').attributes[0].nodeType; //2,節點類型
document.getElementById('box').attributes[0].nodeValue; //屬性值
document.getElementById('box').attributes['id']; //Attr,返回屬性爲 id 的節點
document.getElementById('box').attributes.getNamedItem('id'); //Attr

setAttribute 和 getAttribute

IE中是不認識class屬性的,需改成className屬性,一樣,在Firefox中,也是不認識className屬性的,Firefox只認識class屬性,因此一般作法以下:

element.setAttribute(class, value);  //for firefox
element.setAttribute(className, value);  //for IE

IE:可使用獲取常規屬性的方法來獲取自定義屬性,也可使用getAttribute()獲取自定義屬性
Firefox:只能使用getAttribute()獲取自定義屬性.

解決方法:統一經過getAttribute()獲取自定義屬性

document.getElementById('box').getAttribute('id');//獲取元素的 id 值
document.getElementById('box').id;//獲取元素的 id 值
document.getElementById('box').getAttribute('mydiv');//獲取元素的自定義屬性值
document.getElementById('box').mydiv//獲取元素的自定義屬性值, IE 不支持非
document.getElementById('box').getAttribute('class');//獲取元素的 class 值,IE 不支持
document.getElementById('box').getAttribute('className');//非 IE 不支持

PS:在 IE7 及更低版本的IE瀏覽器中,使用 setAttribute()方法設置 classstyle 屬性是沒有效果的,雖然 IE8 解決了這個 bug,但仍是不建議使用。

removeAttribute()方法

removeAttribute()能夠移除 HTML 屬性。
document.getElementById('box').removeAttribute('style');//移除屬性

PS:IE6 及更低版本不支持 removeAttribute()方法。

跨瀏覽器事件Event對象

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
       #drop{
           width: 300px;
           height: 200px;
           background-color: #ff0000;
           padding: 5px;
           border: 2px solid #000000;
       }
       #item{
           width: 100px;
           height: 100px;
           background-color: #ffff00;
           padding: 5px;
           margin: 20px;
           border: 1px dashed black;
       }
       *[draggable = true]{
           -moz-user-select: none;
           -webkit-user-select: none;
           cursor: move;
       }
    </style>
</head>
<body>
<div>
    <p>將金黃色的小方塊拖到紅色的大方塊中,不兼容IE7及如下瀏覽器,兼容主流瀏覽器!</p>
</div>
<div id="item" draggable="true"></div>
<div id="drop"></div>
<script type="text/javascript">
    function listenEvent(target,type,handler){
        if(target.addEventListener){//w3c
            target.addEventListener(type,handler,false);
        }else if(target.attachEvent){//IE
            type = "on" + type;
            target.attachEvent(type,handler);//IE
        }else{
            target["on" + type] = handler;
        }
    }

    //取消事件
    function cancelEvent(e){
        if(e.preventDefault){
            e.preventDefault();//w3c
        }else{
            e.returnValue = false;//IE
        }
    }
    //取消傳遞
    function cancelPropagation(e){
        if(e.stopPropagation){
            e.stopPropagation();//w3c
        }else{
            e.cancelBubble = true;//IE
        }
    }

    window.onload = function () {
        var target = document.getElementById('drop');
        listenEvent(target,'dragenter',cancelEvent);
        listenEvent(target,"dragover",dragOver);
        listenEvent(target,'drop', function (evt) {
            cancelPropagation(evt);
            evt = evt || window.event;
            evt.dataTransfer.dropEffect = 'copy';
            var id = evt.dataTransfer.getData('Text');
            target.appendChild(document.getElementById(id));
        });

        var item = document.getElementById('item');
        item.setAttribute("draggable",'true');
        listenEvent(item,'dragstart', function (evt) {
            evt = evt || window.event;
            evt.dataTransfer.effectAllowed = 'copy';
            evt.dataTransfer.setData('Text',item.id);
        });
    };

    function dragOver(evt){
        if(evt.preventDefault) evt.preventDefault();
        evt = evt || window.event;
        evt.dataTransfer.dropEffect = 'copy';
        return false;
    }
</script>
</body>
</html>

dataTransfer 對象

| 屬性 | 描述 |
| ------------- |:-------------:|
| dropEffect | 設置或獲取拖曳操做的類型和要顯示的光標類型 |
| effectAllowed | 設置或獲取數據傳送操做可應用於該對象的源元素 |

| 方法 | 描述 |
| ------------- |:-------------:|
| clearData | 經過 dataTransfer 或 clipboardData 對象從剪貼板刪除一種或多種數據格式 |
| getData | 經過 dataTransfer 或 clipboardData 對象從剪貼板獲取指定格式的數據
| setData | 以指定格式給 dataTransfer 或 clipboardData 對象賦予數據

HTML5拖拽的瀏覽器支持

Internet Explorer 九、Firefox、Opera 十二、Chrome 以及 Safari 5 支持拖放

爲了使元素可拖動,需把 draggable 屬性設置爲 true

<img draggable="true" />

| 事件 | 描述 |
| ------------- |:-------------:|
| dragstart | 拖拽事件開始 |
| drag | 在拖動操做上 |
| dragenter | 拖動到目標上,用來決定目標是否接受放置
|dragover | 拖動到目標上,用來決定給用戶的反饋
|drop | 放置發生
| dragleave| 拖動離開目標
|dragend | 拖動操做結束

上述代碼的一些瀏覽器兼容性:

1.爲了兼容IE,咱們將`window.event`賦給 `evt`,其餘瀏覽器則會正確將接收到的`event`對象賦給`evt`。
2.w3c使用addEventListener來爲事件元素添加事件監聽器,而IE則使用attachEvent。addEventListener爲事件冒泡到的當前對象,而attachEvent是window
3.對於事件類型,IE須要加`on + type`屬性,而其餘瀏覽器則不用
4.對於阻止元素的默認事件行爲,下面是w3c和IE的作法:

    e.preventDefault();//w3c   
    e.returnValue = false;//IE
    
5.對於取消事件傳播,w3c和IE也有不一樣的處理機制:
    e.stopPropagation();//w3c
    e.cancelBubble = true;//IE

跨瀏覽器獲取目標對象

//跨瀏覽器獲取目標對象
function getTarget(ev){
    if(ev.target){//w3c
        return ev.target;
    }else if(window.event.srcElement){//IE
        return window.event.srcElement;
    }
}

對於獲取觸發事件的對象,w3cIE也有不一樣的作法:

event.target;//w3c
event.srcElement;//IE

咱們可使用三目運算符來兼容他們:

obj = event.srcElement ? event.srcElement : event.target;

innerText的問題

innerTextIE中能正常工做,可是innerTextFireFox中卻不行。

<p id="element"></p>
<script type="text/javascript">
    if(navigator.appName.indexOf("Explorer") >-1){
        document.getElementById('element').innerText = "my text";
    } else{
        document.getElementById('element').textContent = "my text";
    }
</script>

跨瀏覽器獲取和設置innerText

//跨瀏覽器獲取innerText
function getInnerText(element){
    return (typeof element.textContent == 'string') ? element.textContent : element.innerText;
}
 
//跨瀏覽器設置innerText
function setInnerText(element,text){
    if(typeof element.textContent == 'string'){
        element.textContent = text;
    }else{
        element.innerText = text;
    }
}

oninput,onpropertychange,onchange的用法

onchange觸發事件必須知足兩個條件:

a)當前對象屬性改變,而且是由鍵盤或鼠標事件激發的(腳本觸發無效)

b)當前對象失去焦點(onblur);

onpropertychange的話,只要當前對象屬性發生改變,都會觸發事件,可是它是IE專屬的;

oninput是onpropertychange的非IE瀏覽器版本,支持firefox和opera等瀏覽器,但有一點不一樣,它綁定於對象時,並不是該對象全部屬性改變都能觸發事件,它只在對象value值發生改變時奏效。

訪問XMLHTTPRequest對象

<script type="text/javascript">
    if(window.XMLHttpRequest){
        xhr = new XMLHttpRequest();//非IE
    }else if(window.ActiveXObject){
        xhr = new ActiveXObject("Microsoft.XMLHttp");//IE
    }
</script>

禁止選取網頁內容

問題:  
FF須要用CSS禁止,IE用JS禁止  

解決方法:  
IE: obj.onselectstart = function() {return false;}  
FF: -moz-user-select:none;

三大不冒泡事件

全部瀏覽器的focus/blur事件都不冒泡,萬幸的是大部分瀏覽器支持focusin/focusout事件,不過可惡的firefox連這個都不支持。

IE六、七、8下 submit事件不冒泡。
IE六、七、8下 change事件要等到blur時才觸發。

萬惡的滾輪事件

滾輪事件的支持可謂是亂七八糟,規律以下:

IE6-11 chrome mousewheel wheelDetla 下 -120 上 120

firefox DOMMouseScroll detail 下3 上-3

firefox wheel detlaY 下3 上-3

IE9-11 wheel deltaY 下40 上-40

chrome wheel deltaY 下100 上-100

關於鼠標滾輪事件,IE支持mousewheel,火狐支持DOMMouseScroll
判斷鼠標滾輪是向上仍是向下,IE是經過wheelDelta屬性,而火狐是經過detail屬性

事件委託方法

//事件委託方法  
IE:document.body.onload = inject; //Function inject()在這以前已被實現  
FF:document.body.onload = inject();

HTML5 的瀏覽器支持狀況

圖片描述

圖片描述

來源地址:http://fmbip.com/litmus/

查詢操做

查詢經過指的是經過一些特徵字符串來找到一組元素,或者判斷元素是否是知足字符串。

1. IE6/7不區分id和nam
在IE6/7下使用getElementById和getElementsByName時會同時返回id或name與給定值相同的元素。因爲name一般由後端約定,所以咱們在寫JS時,應保證id不與name重複。

2. IE6/7不支持getElementsByClassName和querySelectorAll   
這兩個函數從IE8開始支持的,所以在IE6/7下,咱們實際能夠用的只有getElementByTagName。

3. IE6/7不支持getElementsByTagName('*')會返回非元素節點    
要麼不用*,要麼本身寫個函數過濾一下。

4. IE8下querySelectorAll對屬性選擇器不友好    
幾乎全部瀏覽器預約義的屬性都有了問題,儘可能使用自定義屬性或者不用屬性選擇器。

5. IE8下querySelectorAll不支持僞類    
有時候僞類是很好用,IE8並不支持,jquery提供的:first、:last、:even、:odd、:eq、:nth、:lt、:gt並非僞類,咱們在任什麼時候間都不要使用它們。

6. IE9的matches函數不能處理不在DOM樹上的元素
只要元素不在dom樹上,必定會返回false,實在不行把元素丟在body裏面匹配完了再刪掉吧,固然了咱們也能夠本身寫匹配函數以免迴流。

to be continued...

資料參考:
http://w3help.org/zh-cn/kb/

http://www.zhihu.com/question...

相關文章
相關標籤/搜索