1.14 JavaScript5:常用DOM操作

尹成發表於2018-11-09

 

 

建立元素

使用document.createElement()可以建立新元素。這個方法只接受一個引數,即要建立元素的標籤名。這個標籤名在HTML文件中不區分大小寫,在XHTML中區分大小寫。

var div = document.createElement("div");

使用createElement()方法建立新元素的同時,也為新元素設定了ownerDocument屬性,可以操作元素的特性。

div.id = "myDiv";
div.className = "div1";

此時,新元素尚未被新增到文件樹中,因此設定各種特性均不會影響瀏覽器的顯示。要新增到文件樹,可用appendChild()、insertBefore()、replaceChild()。(稍後講到)

document.body.appendChild(div);

當把元素新增到文件樹中後,伺候鬼這個元素做的任何修改都會實時地反應到瀏覽器中。

在IE中可以為createElement()方法傳入完整的元素標籤和屬性。(只在IE中相容)

var div = document.createElement("<div id=\"mydiv\" class=\"div1\"></div>");

不能再標籤里加其他元素節點或者文字節點,如下的方式和上面的得出的節點一樣

var div = document.createElement("<div id=\"mydiv\" class=\"div1\">12212</div>"); 

建立文字節點

使用document.createTextNode()來建立文字節點,這個方法接受一個引數:要插入節點的文字。與設定已有文字節點的值一樣,作為引數的文字將按照HTML或XML的格式進行編碼。

document.createTextNode("121212");

可以新增多個文字節點。假如兩個文字節點時相鄰的同胞節點,那麼兩個文字節點會連起來,中間不會有空格。

節點關係

(IE9以前不將換行和空格看做文字節點,其他瀏覽器會)

文字關係如下:

<div id="div1">
    <div id="div2">2</div>
    <div id="div3">3</div>
    <div id="div4">4</div>
</div>

父節點:parentNode

parentNode是指定節點的父節點.一個元素節點的父節點可能是一個元素(Element )節點,也可能是一個文件(Document )節點,或者是個文件碎片(DocumentFragment)節點. 
每一個節點都有一個parentNode屬性。

對於下面的節點型別: Attr, Document, DocumentFragment, Entity, Notation,其parentNode屬性返回null。如果當前節點剛剛被建立,還沒有被插入到DOM樹中,則該節點的parentNode屬性也返回null。

<script type="text/javascript">
    var child2 = document.getElementById("div2");
    var parent = child2.parentNode;
</script>

子節點:childNodes

childNodes 返回包含指定節點的子節點的集合,該集合為即時更新的集合(live collection)。 
即時更新就是對節點元素的任意修改都會立即反映到結果裡。

<script type="text/javascript">
    var child2 = document.getElementById("div2");
    var parent = child2.parentNode;
    var allChilds = parent.childNodes;
    console.log(allChilds.length) // IE下是3,其他瀏覽器是7
    var nodeAdd = document.createElement("div");
    var textAdd = document.createTextNode("這是新增的文字節點");
    nodeAdd.appendChild(textAdd);
    parent.appendChild(nodeAdd);
    console.log(allChilds.length);// IE下是4,其他瀏覽器是8
</script>

兄弟節點:nextSibling,previousSibling

nextSibling返回某節點的下一個兄弟節點,previousSibling返回某節點的上一個兄弟節點,沒有的話返回null。 
注意:可能因為元素換行的原因返回的是text節點。

<script type="text/javascript">
    var child3 = document.getElementById("div3");
    var next = child3.nextSibling;
    var previous = child3.previousSibling;
    console.log(next); // IE下返回div4,其他返回text
    console.log(previous)  // IE下返回div2,其他返回text
</script>

第一個或最後一個子節點:firstChild、lastChild

firstChild返回node的子節點中的第一個節點的引用,沒有返回null 
lastChild返回node的子節點中的最後一個節點的引用,沒有返回null

節點元素關係

只算元素,不算文字節點。

以下三個方法用法和節點關係完全一樣,只是這三個方法只看元素節點,不管因為空格、換行造成的文字節點或者手動加上去的文字節點。 
children: 返回所有元素子節點(IE5+、ff3.5、opera3、chrome,但在IE8及以下會將註釋節點看成一個元素節點)

以下兩個IE9+才支援 
nextElementSibling:返回元素的下一個兄弟元素節點 
previousElementSibling: 返回元素的上一個兄弟元素節點

節點操作

appendChild()

appendChild()用於向childNodes列表的末尾新增一個節點,並且返回這個新增的節點。 
如果傳入到appendChild()裡的節點已經是文件的一部分了,那結果就是將節點從原來的位置轉移到新位置,任何一個節點不能同時出現在文件中的多個位置。

    var returnNode = someNode.appendChild(someNode.firstChild); // 返回第一個節點
    console.log(returnNode === someNode.firstChild); // false
    console.log(returnNode === someNode.lastChild); // true

insetBefore()

insetBefore()可以將節點插入到某個特定的位置。這個方法接受兩個引數:要插入的節點和作為參照的節點。 
插入節點後,被插入的節點變成參照節點的前一個同胞節點,同時被方法返回。 如果參照節點是null,則與appendChild()執行相同的操作。

    // 插入後成為最後一個子節點
    var returnNode = someNode.insetBefore(newNode, null);
    console.log(returnNode === someNode.lastChild); // true
    // 插入後成為第一個子節點
    var returnNode = someNode.insetBefore(newNode, someNode.firstChild);
    console.log(returnNode === newNode); // true
    console.log(returnNode === someNode.firstChild); // true
    // 插入到最後一個子節點的前面
    var returnNode = someNode.insetBefore(newNode, someNode.lastChild);
    console.log(returnNode === someNode.childNodes[someNode.childnodes.length - 2]) // true

替換節點: replaceChild()

replaceChild()接受兩個引數:要插入的節點和要被替換的節點。被替換的節點將由這個方法返回並從文件中被移除,同時由要插入的節點佔據其位置。

// 替換第一個子節點

var returnNode = someNode.replaceChild(newNode, someNode.firstChild);

使用replaceChild()後,被替換的節點的所有關係指標都會被複制到插入的節點上面。

刪除節點:removeChild()

該方法移除節點,接受一個引數,即要移除的節點,同時該方法返回被移除的節點。只能是一個節點,不能是一組節點。

   // 移除第一個子節點
    var returnNode = someNode.removeChild(newNode, someNode.firstChild);

克隆節點:cloneNode(true/false)

返回撥用該方法的節點的一個副本。參數列示是否採用深度克隆,如果為true,則該節點的所有後代節點也都會被克隆,如果為false,則只克隆該節點本身,文字或者換行、空格這些不會複製,因為他們都是一個textNode。

注意: 在DOM4規範中(實現於Gecko 13.0(Firefox 13.0 / Thunderbird 13.0 / SeaMonkey 2.10) , 檢視 bug 698391),deep是一個可選引數. 如果省略的話, deep引數的預設值為true,也就是說,深度克隆是預設的.如果想使用淺克隆, 你需要將該引數指定為false。

在舊版本的瀏覽器中, 你始終需要指定deep引數。

克隆一個元素節點會拷貝它所有的屬性以及屬性值,當然也就包括了屬性上繫結的事件(比如onclick=”alert(1)”),但不會拷貝那些使用addEventListener()方法或者node.onclick = fn這種用JavaScript動態繫結的事件。

注意:為了防止一個文件中出現兩個ID重複的元素,使用cloneNode()方法克隆的節點在需要時應該指定另外一個與原ID值不同的ID

   var div1 = document.getElementById("div1");
    var cloneHtml = div1.cloneNode(true);
    document.body.appendChild(cloneHtml);

元素選擇

HTML程式碼示例:

  <div id="div1">
        <p id="div2" class="one" name="nameone">2</p>
        <div id="div3">3</div>
        <div id="div4" name="div2">4</div>
    </div>

querySelector、querySelectorAll(IE8及以上)

Selectors API通過匹配一組選擇器的方式來為從DOM中檢索Element節點提供一些簡單快捷的方法,這比過去必須要在javascript程式碼中用迴圈來查詢某個你想要的特定元素更快一些。 
該規範對於使用Document,DocumentFragment和Element介面的物件都增了兩種新方法:

  • querySelector:返回節點子樹內與之相匹配的第一個Element節點。如果沒有匹配的節點,則返回null。
  • querySelectorAll:返回一個包含節點子樹內所有與之相匹配的Element節點列表,如果沒有相匹配的,則返回一個空節點列表。

注意:由 querySelector()、querySelectorAll()返回的節點列表不是動態實時的(非live Collection)。這和其他DOM查詢方法返回動態實時節點列表不一樣。

選擇器方法接受一個或多個用逗號分隔的選擇器來確定需要被返回的元素。例如,要選擇文件中所有CSS的類(class)是warning或者note的段落(p)元素,可以這樣寫:

var special = document.querySelectorAll( "p.warning, p.note" );

也可以通過ID來查詢,例如:

var el = document.querySelector( "#main, #basic, #exclamation" );

執行上面的程式碼後,el就包含了文件中元素的ID是main,basic或exclamation的所有元素中的第一個元素。

querySelector() and querySelectorAll() 裡可以使用任何CSS選擇器,他們都不是live Collection:

 var notLive = document.querySelectorAll("p");
    console.log(notLive);
    document.getElementById("div1").removeChild(document.getElementById("div2"));
    console.log(notLive);
    // 上面兩個輸出都是輸出 `p#div2.one`的引用,沒有因為刪除了`p`標籤而使`notLive`的結果發生變化。

getElementById()

返回一個匹配特定 ID的元素。id是大小寫敏感的字串,代表了所要查詢的元素的唯一ID,如果沒有則返回null。 
如果新建一個元素,還沒有插入到文件中,則不能通過該方法獲取到。

  var notLive = document.getElementById("div2");
    console.log(notLive.innerHTML);
    document.getElementById("div1").removeChild(document.getElementById("div2"));
    console.log(notLive.innerHTML);
    // 上面輸出都是2,說明getElementById()也是**非**live collection

getElementsByTagName()

document.getElementsByTagName() 方法返回一個實時的包含具有給出標籤名的元素們的HTMLCollection。指定的元素的子樹會被搜尋,包括元素自己。返回的 list 是實時的(live collection),意味著它會隨著DOM樹的變化自動更新。因此,如果對同一個元素,使用相同的引數,是不需要多次呼叫document.getElementsByTagName() 的。

Element.getElementsByTagName()的搜尋被限制為指定元素的後代而不是document

  var live = document.getElementsByTagName("p");
    console.log(live[0].innerHTML);
    document.getElementById("div1").removeChild(document.getElementById("div2"));
    console.log(live[0].innerHTML);
    // 第一個輸出2,第二個報錯,因為無法引用到p標籤

getElementsByName()

該方法返回一個實時的nodelist collection,包含文件中所有name屬性匹配的標籤。這是一個live collection。 
注意:在IE和opera下,如果某個元素1的name和另一個元素2的id重合,且元素2在元素1的前面,則getElementsByName()會取到元素2。

  var live = document.getElementsByName("div2");
    console.log(live[0].innerHTML);
    document.getElementById("div1").removeChild(document.getElementById("div2"));
    console.log(live[0].innerHTML);
    // chrome下:全部輸出4
    // IE下: 第一個輸出2,第二個報錯。

getElementsByClassName()

該方法返回一個即時更新的(live) HTMLCollection,包含了所有擁有指定 class 的子元素。當在 document 物件上呼叫此方法時,會檢索整個文件,包括根元素。(IE9以下不支援)

要匹配多個class,則className用空格分開。

getElementsByClassName("class1 class2");
    var live = document.getElementsByClassName("one");
    console.log(live[0].innerHTML);
    document.getElementById("div1").removeChild(document.getElementById("div2"));
    console.log(live[0].innerHTML);
    // 第一個返回2,第二個報錯

屬性操作

setAttribute()

新增一個新屬性(attribute)到元素上,或改變元素上已經存在的屬性的值。

當在 HTML 文件中的 HTML 元素上呼叫 setAttribute() 方法時,該方法會將其屬性名稱(attribute name)引數小寫化。

如果指定的屬性已經存在,則其值變為傳遞的值。如果不存在,則建立指定的屬性。也可指定為null。如果設定為null,最好使用removeAttribute()。

  var div2 = document.getElementById("div2");
    div2.setAttribute("class", "new_class");
    div2.setAttribute("id", "new_id");

注意:在IE7下,修改了元素的class,如果已有class,則會出現兩個class,通過setAttribute()新增的不生效;如果沒有class,則新增上class,但這個新增上去的class的樣式不會生效。

removeAttribute()

該方法用於移除元素的屬性。

   var div2 = document.getElementById("div2");
    div2.removeAttribute("class");

注意:IE7下無法移除 class 屬性

getAttribute()

該方法返回元素上指定屬性(attribute)的值。如果指定的屬性不存在,則返回 null 或 “” (空字串)(IE5+都返回null)。

  var div2 = document.getElementById("div2");
    var attr = div2.getAttribute("class");
    console.log(attr);

注意:IE7下不能正確返回class,返回的是null,其他正常。

hasAttribute()

hasAttribute() 返回一個布林值,指示該元素是否包含有指定的屬性(attribute)。

注意:IE7不支援該方法。

自定義屬性data-*

html5裡有一個data-*去設定獲取元素的自定義屬性值。

<div id="div1" data-aa="11">

利用div1.dataset可以獲得一個DOMStringMap,包含了元素的所有data-*。 
使用div1.dataset.aa就可以獲取11的值。 
同樣,通過設定div1.dataset.bb = “22”就可以設定一個自定義屬性值。 
在不相容的瀏覽器裡,就使用getAttribute和setAttribute

 var div1 = document.getElementById("div1");
    var a = null;
    if (div1.dataset) {
        a = div1.dataset.aa;
        div1.dataset.bb = "222";
    } else {
        a = div1.getAttribute("data-aa");
        div1.setAttribute("data-bb", "2222");
    }
    console.log(a);

事件

addEventListener()

addEventListener()將指定的事件監聽器註冊到目標物件上,當目標物件觸發制定的事件時,指定的回撥函式就會觸發。目標物件可以是 文件上的元素、 document、 window 或者XMLHttpRequest(比如onreadystatechange事件)。

IE8及以下不支援此方法且只有事件冒泡沒有事件捕獲。IE9開始支援此方法,也就有了事件捕獲。

 var div1 = document.getElementById("div1");
    div1.addEventListener("click", listener, false);
    function listener() {
        console.log('test');
    }
    var cloneHtml = div1.cloneNode(true);
    document.body.appendChild(cloneHtml);

第一個引數是事件名,第二個是回撥函式,第三個引數為true表示捕獲,false表示冒泡。

 var div1 = document.getElementById("div1");
    div1.addEventListener("click", listener1, true/fasle);
    function listener1() {
        console.log('test1');
    }
    var div2 = document.getElementById("div2");
    div2.addEventListener("click", listener2, true/fasle);
    function listener2() {
        console.log('test2');
    }

有一點要注意的是,當對某一個元素1既繫結了捕獲事件,又繫結了冒泡事件時: 
當這個元素1並不是觸發事件的那個元素2時,則觸發順序會按照先 捕獲 後 冒泡 的順序觸發; 
當這個元素1就是最底層的觸發事件的元素時,則這個元素沒有捕獲和冒泡的區別,誰先繫結就先觸發誰。

  var div2 = document.getElementById("div2");
    div2.addEventListener("click", listener2, true);
    function listener2() {
        console.log('test2');
    }
    div2.addEventListener("click", listener1, false);
    function listener1() {
        console.log('test1');
    }
    // 按繫結順序執行,兩個`addEventLister()`顛倒過來則執行順序也變化
    // 如果再對`div1`繫結一個捕獲、一個冒泡,則會先觸發捕獲 再 觸發冒泡,與繫結順序無關

removeEventListener()

與addEventListener()繫結事件對應的就是移除已繫結的事件。第三個引數的布林值代表解綁的是捕獲事件還是冒泡事件。兩個事件互不相關。

  var div2 = document.getElementById("div2"); 
    div2.addEventListener("click", listener2, true);
    function listener2() {
        console.log('test2');
    }
    div2.removeEventListener("click", listener2, true);

注意:只能通過removeEventListener()解綁有名字的函式,對於繫結的匿名函式無法解除繫結。

 div2.addEventListener("click", function(){
        console.log('test');
        console.log(this);
    }, true);
    div2.removeEventListener("click", function() {
        console.log("test");
    }, true);
    div2.onclick = null;
    // 點選div2依然列印出test

注意:這裡this指向觸發事件的元素自身。

attachEvent()、detachEvent()

IE8及以下使用這兩個方法繫結和解綁事件,當然,IE9+也支援這個事件。但這個方法繫結的事件預設為冒泡也只有冒泡。

 // 這裡需要在事件前加 on
    div2.attachEvent("onclick", listener1);
    function listener1() {
        console.log('test');
        console.log(this);
    }
    div2.detachEvent("onclick", listener1);

和addEventListener()一樣,也不能解綁匿名函式。 
注意:這裡this指向 window。

阻止預設事件和冒泡

標準事件和IE事件中的阻止預設事件和冒泡事件也有很大區別。

  var div2 = document.getElementById("div2");
    if (div2.addEventListener) {
        div2.addEventListener("click", function(e) {
            e.preventDefault(); // 阻止預設事件
            e.stopPropagation(); // 阻止冒泡
            console.log(e.target.innerHTML);
        }, false);
    } else {
        div2.attachEvent("onclick", function() {
            var e = window.event;
            e.returnValue = false; // 阻止預設事件
            e.cancelBubble = true; // 阻止冒泡
            console.log(e.srcElement.innerHTML);
        });
    }

IE8及以下的event是繫結在window上的。(我的IE11裡,模擬到IE7、IE8也可以取到標準事件裡的 e 物件,估計是升級到IE11的原因)。

自定義事件:createEvent()

createEvent()用於建立一個新的 event ,而後這個 event 必須呼叫它的 init() 方法進行初始化。最後就可以在目標元素上使用dispatchEvent()呼叫新建立的event事件了。

createEvent()的引數一般有:UIEvents、MouseEvents、MutationEvents、HTMLEvents、Event(s)等等,分別有對應的init()方法。HTMLEvents、Event(s)對應的都是initEvent()方法。

initEvent(type, bubbles, cancelable) 
type表示自定義的事件型別,bubbles表示是否冒泡,cancelable表示是否阻止預設事件。

target.dispatchEvent(ev) 
target就是要觸發自定義事件的DOM元素

 var div1 = document.getElementById("div1");
        div1.addEventListener("message", function(){
            console.log('test');
        }, false);
        var div2 = document.getElementById("div2");
        div2.addEventListener("message", function(e){
            console.log(this);
            console.log(e);
        }, false);
        var ev = document.createEvent("Event");
        ev.initEvent("message", false, true); // 起泡引數變為true,div1的事件就會觸發
        div2.dispatchEvent(ev);

獲取元素相關計算後的值

getComputedStyle()、currentStyle()

當我們想獲取元素計算後實際呈現在頁面上的各個值,就用這兩個方法。IE8及以下用currentStyle(),IE9+及其他標準瀏覽器用getComputedStyle()。

 var div2 = document.getElementById("div2");
    var result = "";
    if (window.getComputedStyle) {
        result = (window || document.defaultView).getComputedStyle(div2, null)['cssFloat'];
    } else {
        result = div2.currentStyle["styleFloat"];
    }
    console.log(result);
    // document.defaultView返回document物件所關聯的window

這兩個方法在不同的瀏覽器裡差距也很大。 
比如float屬性: 
getComputedStyle: IE9以上需要用cssFloat,其他標準的用float 
currentStyle: IE8及以下可用styleFloat或者float。

比如height屬性: 
假如未設定height值,標準瀏覽器裡能計算出高度值,而currentStyle計算出來是auto。

上面的例子getComputedStyle是用鍵值去訪問的,也可用getPropertyValue()去訪問。(IE8、IE7不支援)

result = (window || document.defaultView).getComputedStyle(div2, null).getPropertyValue("float");

getBoundingClientRect()、getClientRects()

getBoundingClientRect()該方法獲得頁面中某個元素的上、右、下、左分別相對瀏覽器視窗的位置。getBoundingClientRect是DOM元素到瀏覽器可視範圍的距離(到瀏覽器頂部而不是文件頂部)。該函式返回一個Object物件,該物件有6個屬性:top,lef,right,bottom,width,height;這裡的top、left和css中的理解很相似,width、height是元素自身的寬高,但是right,bottom和css中的理解有點不一樣。right是指元素右邊界距視窗最左邊的距離,bottom是指元素下邊界距視窗最上面的距離。

getClientRects()是返回一個ClientRectList集合。

 var div1 = document.getElementById("div1");
    var rects1 = div1.getClientRects();
    var rects2 = div1.getBoundingClientRect();
    console.log(rects1[0].top);
    console.log(rects2.top);

 

 

相關文章