被人忽視的 DOM API

在框架盛行的年代,還有多少人記得在沒有框架時我們?nèi)绾慰刂?dom 的行為呢?作者本人也一直忽視了這方面的學(xué)習(xí),直到面試問到這個問題智袭,下決心好好認(rèn)識認(rèn)識這個 dom api。

node掠抬、document 和 element

在學(xué)習(xí) dom api 時對這三者還是挺混亂的吼野。理一下他們之間的關(guān)系。

node

node 是一個接口两波,像 document 和 element 都是繼承這個接口的瞳步。這個接口提供了 dom 節(jié)點的獲取和操作方法闷哆。
node 有許多類型,下圖列出了一些 node 的類型碼单起。由圖可見 element 的類型碼為 1抱怔,文本節(jié)點類型碼為 3,注釋節(jié)點類型碼為 8嘀倒,document 的類型碼為 9野蝇。

node type

這讓我想到了 vue.js 中出現(xiàn)多次的代碼:if (child.nodeType === 3) { ... } 其實就是判斷當(dāng)前節(jié)點是否為文本節(jié)點。

element

從上面的內(nèi)容可知括儒,element 就是一個特殊的 node(nodeType == 1)绕沈,其實 element 就是 HTML 各類標(biāo)簽,如 <p><div> 這類有特殊含義的帮寻,能夠攜帶一些特殊屬性的節(jié)點乍狐。所以說 element 可以用 node 的所有 api。

document

同理固逗,document 也是一個特殊的 node浅蚪,它與 element 的不同之處在于 document 通常是 DOM 節(jié)點,即包含有 head 和 body 元素的一個 node烫罩。

參考:Difference between Node object and Element object? - Stack Overflow

api 學(xué)習(xí)

首先我發(fā)現(xiàn)一點:所有 dom 操作的起點都是使用 document 去獲取各種類型的 node (集合)然后再去執(zhí)行各類 dom 操作的行為惜傲!
由于 document 操作的 api 真的很多,所以我選取我想了解的部分學(xué)習(xí)了贝攒。在這里我學(xué)習(xí) DOM API 的目的是:

學(xué)習(xí) document 對象操作 dom 的方式盗誊,擁有脫離框架(jquery、vue等)來操作 dom 的能力隘弊。

獲取節(jié)點

  • Document.documentElement 返回 document 的直屬后代元素哈踱。
  • Document.activeElement 返回當(dāng)前正在操作的元素
  • Document.body 返回當(dāng)前文檔的 <body> 元素。與此類似的還有 Document.head 和 Document.scripts 兩個屬性返回當(dāng)前文檔的 <head><script> 元素梨熙。
  • Document.getElementByClassName() 返回有給定樣式名的元素列表
  • Document.getElementByTagName() 返回有給定標(biāo)簽名的元素列表
  • document.getElementById() 返回一個對識別元素的對象引用
  • document.querySelector() 返回文檔中第一個匹配指定選擇器的元素
  • document.querySelectorAll() 返回一個匹配指定選擇器的元素節(jié)點列表
  • Node.childNodes 返回一個包含了該節(jié)點所有子節(jié)點的實時的 NodeList 是“實時的”意思是开镣,如果該節(jié)點的子節(jié)點發(fā)生了變化,NodeList 對象就會自動更新咽扇。
  • Node.firstChild & Node.lastChild 返回該節(jié)點的第一個子節(jié)點或最后一個子節(jié)點邪财,如果該節(jié)點沒有子節(jié)點則返回 null。
  • Node.previousSibling & Node.nextSibling 返回與該節(jié)點同級的上一個或下一個節(jié)點质欲,如果沒有返回null树埠。
  • Node.ownerDocument 返回這個元素屬于的 Document 對象 。
  • Node.parentNode 返回一個當(dāng)前結(jié)點 Node 的父節(jié)點 把敞。
  • Node.parentElement 返回一個當(dāng)前節(jié)點的父節(jié)點 Element弥奸。

操作節(jié)點

  • Document.createComment() 創(chuàng)建一個新的注釋節(jié)點并返回它
  • Document.createDocumentFragment() 創(chuàng)建一個新的文檔片段
  • Document.createElement() 用給定的標(biāo)簽名創(chuàng)建一個新的元素榨惠。
  • Document.createTextNode() 創(chuàng)建一個文字節(jié)點
  • Document.write() 向文檔中寫入內(nèi)容(與之有類似功能的是 Document.writeln() 不同之處在于后面多了個換行符奋早。)
  • Element.innerHTML 設(shè)置或返回元素的內(nèi)容
  • Node.textContent 獲取或設(shè)置一個標(biāo)簽內(nèi)所有子結(jié)點及其后代的文本內(nèi)容盛霎。
  • Node.appendChild() 向元素添加新的子節(jié)點,作為最后一個子節(jié)點耽装。
  • Node.cloneNode() 克隆元素(方法中傳參為deep愤炸,如果deep為true則深拷貝。)
  • Node.insertBefore() 在指定已有節(jié)點前插入新節(jié)點(沒有 insertAfter 方法掉奄」娓觯可以使用 insertBefore 方法和 nextSibling 來模擬它。)
  • Node.normalize() 合并元素中相鄰文本節(jié)點
  • Node.removeChild() 從元素中移除子節(jié)點
  • Node.replaceChild() 替換元素中的子節(jié)點

其中 Document 的 createXXX 方法還有一些其他不常用的姓建,如需使用請查閱 MDN诞仓。

其他常用屬性和方法

  • Element.classList 返回元素的 class 集合。
  • EventTaget.addEventListener() 注冊監(jiān)聽事件
  • Node.nodeType 返回該節(jié)點的類型碼
  • Node.nodeValue 返回或設(shè)置當(dāng)前節(jié)點的值速兔。
  • Node.compareDocumentPosition() 比較當(dāng)前節(jié)點與任意文檔中的另一個節(jié)點的位置關(guān)系墅拭。
  • Node.contains() 傳入的節(jié)點是否為該節(jié)點的后代節(jié)點。
  • Node.hasChildNodes() 是否擁有子節(jié)點
  • Node.isEqualNode() 檢查兩個元素是否相等
  • Node.isSameNode() 檢查兩個元素是否為相同的節(jié)點

以上內(nèi)容均參考了 MDN 上的內(nèi)容:

寫個操作 DOM 的例子

接下來就使用這些 API 來進行一些 DOM操作涣狗。

獲取各個位置的節(jié)點谍婉。

這里寫了個小demo:

<div id="container">
    <div>
        <h1>get dom</h1>
        <ul id="list">
            <li><span>hello world 1</span></li>
            <li><span>hello world 2</span></li>
            <li><span>hello world 3</span></li>
            <li><span>hello world 4</span></li>
            <li><span>hello world 5</span></li>
        </ul>
    </div>
    <br/>
    <div>
        <button>commit</button>
    </div>
</div>

<script>
    var container = document.getElementById('container')
    console.log('列出所有node', container.childNodes)
    var h1 = document.getElementsByTagName('h1')[0]
    console.log('獲取h1后的元素', h1.nextSibling)
    var uldiv = container.firstChild
    while (uldiv && uldiv.nodeType != 1) {
        uldiv = uldiv.nextSibling
    }
    var ul = uldiv.lastChild
    while (ul && ul.nodeType == 3) {
        ul = ul.previousSibling
    }
    console.log('獲取ul中第一個元素內(nèi)容', ul.firstChild)
    var doc = h1.ownerDocument
    console.log('獲取當(dāng)前 Document 對象', doc)
    var li1 = ul.firstChild
    console.log('獲取li的父級節(jié)點', li1.parentElement)
    var button = document.getElementsByTagName("button")[0]
    button.onclick = log
    button.focus()
    button.click()
    console.log('獲取正在操作的元素', document.activeElement)

    function log(){
        console.log('button is clicked')
    }
</script>

最后返回結(jié)果如圖:

返回結(jié)果

由于在 chrome 中空格、換行算是文本節(jié)點镀钓。所以獲取最后元素的時候總是會獲取到那些文本節(jié)點上去穗熬。這個要注意的。所以我在代碼中使用 nodeType == 1 來區(qū)分是否為元素丁溅。
在上面例子中查找了各種關(guān)系的元素唤蔗,解決日常元素獲取問題應(yīng)該不難了。

實踐創(chuàng)建node窟赏、插入node和刪除node措译。

<div>
    <div id="container">
        <h2 id="child">Hello Child</h2>
    </div>
    <br/>
    <div id="buttonGroup"></div>
</div>

<script>
    // 父節(jié)點向子節(jié)點插入元素
    function appendChild(){
        var container = document.getElementById("container")
        var text = document.createElement("h2")
        text.textContent = 'Hello New Child'
        container.appendChild(text)
    }
    // 子節(jié)點獲取父節(jié)點,在父節(jié)點后插入元素
    function appendParent(){
        var child = document.getElementById('child')
        var parent = child.parentElement
        var text = document.createElement("h1")
        text.textContent = 'Hello Parent'
        var root = parent.parentElement

        root.insertBefore(text, parent.nextSibling)
    }
    // 在當(dāng)前元素前插入元素
    function appendPre(){
        var child = document.getElementById('child')
        var text = document.createElement("h2")
        text.textContent = 'Hello Pre Child'
        child.parentElement.insertBefore(text, child)
    }
    // 在當(dāng)前元素后插入元素
    function appendNext(){
        var child = document.getElementById('child')
        var text = document.createElement("h2")
        text.textContent = 'Hello Next Child'
        child.parentElement.insertBefore(text, child.nextSibling)
    }
    // 移除父元素中最后一個子元素
    function removeEle(){
        var container = document.getElementById('container')
        if (container.lastChild) {
            container.removeChild(container.lastChild)
        }
    }
    // 替換父元素中的子元素
    function replaceEle(){
        var child = document.getElementById("child")
        var newNode = document.createElement('div')
        newNode.innerHTML = "<button>button</button>hello new node replaced"
        var parent = child.parentElement
        parent.replaceChild(newNode, child)
    }
    // 創(chuàng)建按鈕組
    var ButtonGroup = document.getElementById("buttonGroup")
    var EventList = [ 
        "appendChild", 
        "appendParent", 
        "appendPre", 
        "appendNext", 
        "removeEle", 
        "replaceEle" 
    ]

    var ButtonArr = []
    for (var key of EventList) {
        var btn = document.createElement('button')
        btn.textContent = key
        btn.onclick = eval(key)
        ButtonArr.push(btn)
    }
    for (var b of ButtonArr) {
        ButtonGroup.appendChild(b)
    }
</script>

以上代碼實現(xiàn)了在各個位置插入元素元素的刪除替換饰序,點擊此處查看運行結(jié)果领虹。

簡單實現(xiàn) v-for、v-text求豫、v-html塌衰、v-on 和 v-model 這些功能。

好吧蝠嘉,作為一個 Vue.js 愛好者最疆,繞不開的想到了 Vue.js 操作 DOM 的一些功能。這里就試著簡單實現(xiàn)下(不涉及 Virtual DOM蚤告,只是單純的 DOM 修改)努酸。
如果對 Vue 命令不了解可以去官網(wǎng)看看這些指令的用法。

<html>
    <head>
        <meta charset="UTF-8">
        <title>Hello Ele</title>
    </head>
    <body>
        <div id="container">
            <h1>v-text</h1>
            <span>{{ message }}</span>
            <h1>v-html</h1>  
            <div v-html="messagespan"></div>          
            <h1>v-model</h1>
            <input v-model="message"/>
            <h1>v-on</h1>
            <input id="myInput" v-on:blur="blur" v-on:focus="focus"/>
            <h1>v-for</h1>
            <ul></ul>
        </div>
        
        <script>
            // v-text
            var message = "Hello World"
            var messagespan = "<span>Hello World</span>"
          
            var spans = document.getElementsByTagName("span")
            for (var span of spans) {
                if (span.textContent == "{{ message }}") {
                    span.textContent = message
                }
            }
            // v-html
            var container = document.getElementById("container")
            var divs = container.getElementsByTagName("div")
            for(var div of divs){
                if (div.getAttribute("v-html") == "messagespan") {
                    div.innerHTML = messagespan
                }
            }
            // v-model
            var inputs = container.getElementsByTagName("input")
            for (var input of inputs) {
                if (input.getAttribute("v-model") == "message") {
                    input.setAttribute("value", message)
                }
            }
            // v-on
            var myInput = document.getElementById("myInput")
            myInput.onfocus = eval(myInput.getAttribute("v-on:focus"))
            myInput.onblur = eval(myInput.getAttribute("v-on:blur"))

            function focus(){
                myInput.setAttribute("value", "focus")
            }

            function blur() {
                myInput.setAttribute("value", "blur")
            }
            // v-for
            var liContents = [
                "jack",
                "rose",
                "james",
                "wade",
                "jordan"
            ]

            var liElementList = []
            for(var content of liContents) {
                var li = document.createElement("li")
                li.innerHTML = `<label><input type="checkbox"/><span>${content}</span></label>`
                liElementList.push(li)
            }
            var ul = container.getElementsByTagName("ul")[0]
            for (var liEle of liElementList) {
                ul.appendChild(liEle)
            }
        </script>
    </body>
</html>

點擊此處看效果杜恰。最后結(jié)果如圖:

顯示結(jié)果

簡單實現(xiàn)了 Vue.js 指令的這些功能获诈,其實在 Vue.js 源碼中也是用了這些 dom 操作的 api 來做的仍源。
更多 Vue 源碼中的 DOM 操作可以看下我的 《Vue.js 源碼學(xué)習(xí)六 —— VNode虛擬DOM學(xué)習(xí)》這篇文章中。

最后

無論什么框架舔涎,其實都是萬變不離其宗笼踩。最終都是用最基礎(chǔ)的 API 來實現(xiàn)的各種功能。所以學(xué)好基礎(chǔ)知識是非常重要的~
PS:還是 MDN 靠譜亡嫌,w3school 的資料雖然也挺多嚎于,但是感覺不是很靠譜……以后查資料盡量去 MDN 英文網(wǎng)站去查(中文網(wǎng)站翻譯有些問題)。

打個廣告

鏈家上海研發(fā)中心招聘前端挟冠、后端于购、測試。
機會不多知染,需要內(nèi)推機會的請將簡歷發(fā)送至 dingxiaojie001@ke.com价涝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市持舆,隨后出現(xiàn)的幾起案子色瘩,更是在濱河造成了極大的恐慌,老刑警劉巖逸寓,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件居兆,死亡現(xiàn)場離奇詭異,居然都是意外死亡竹伸,警方通過查閱死者的電腦和手機泥栖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勋篓,“玉大人吧享,你說我怎么就攤上這事∑┫” “怎么了钢颂?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拜银。 經(jīng)常有香客問我殊鞭,道長,這世上最難降的妖魔是什么尼桶? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任操灿,我火速辦了婚禮,結(jié)果婚禮上泵督,老公的妹妹穿的比我還像新娘趾盐。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布救鲤。 她就那樣靜靜地躺著久窟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蜒简。 梳的紋絲不亂的頭發(fā)上瘸羡,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天漩仙,我揣著相機與錄音搓茬,去河邊找鬼。 笑死队他,一個胖子當(dāng)著我的面吹牛卷仑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播麸折,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼锡凝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垢啼?” 一聲冷哼從身側(cè)響起窜锯,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芭析,沒想到半個月后锚扎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡馁启,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年驾孔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惯疙。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡翠勉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霉颠,到底是詐尸還是另有隱情对碌,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布蒿偎,位于F島的核電站俭缓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏酥郭。R本人自食惡果不足惜华坦,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望不从。 院中可真熱鬧惜姐,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至条舔,卻和暖如春枫耳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孟抗。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工迁杨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人凄硼。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓铅协,卻偏偏與公主長得像,于是被迫代替她去往敵國和親摊沉。 傳聞我的和親對象是個殘疾皇子狐史,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容

  • ??DOM(文檔對象模型)是針對 HTML 和 XML 文檔的一個 API(應(yīng)用程序編程接口)。 ??DOM 描繪...
    霜天曉閱讀 3,649評論 0 7
  • 原生DOM接口挺多的肪康,需要花點時間研究下尼斧,不過先把基礎(chǔ)整好姜贡,后面框架估計好學(xué)點。 1. DOM是啥 1.1 知識回...
    吳少在coding閱讀 1,811評論 0 7
  • ??DOM 1 級主要定義的是 HTML 和 XML 文檔的底層結(jié)構(gòu)。 ??DOM2 和 DOM3 級則在這個結(jié)構(gòu)...
    霜天曉閱讀 1,446評論 1 3
  • 當(dāng)下很流行一句話:“人丑就要多讀書”律秃,就為了這句話爬橡,朵拉兒跟媽媽整整冷戰(zhàn)了一個月。朵拉兒正是進入了高考倒計時...
    胡岱閱讀 316評論 0 0
  • 同意啊你說話費
    暢華南閱讀 64評論 0 0