html dom 高级,DOM 高级工程师不完全指南
原標(biāo)題:DOM 高級工程師不完全指南
本文轉(zhuǎn)載于 SegmentFault 社區(qū)專欄:凱里的前端專欄
譯者:kyrieliu
“我不敢徒手撕 DOM 了”
絕大多數(shù)前端er都有這樣的困擾,但本著基礎(chǔ)為大的原則,手撕 DOM 應(yīng)當(dāng)是一個(gè)前端攻城獅的必備技能,這正是本文誕生的初衷 —— DOM 并沒有那么難搞,如果能去充分利用它,那么你離愛上它就不遠(yuǎn)了。
三年前我初入前端坑的時(shí)候,發(fā)現(xiàn)了一個(gè)叫做 jQuery 的寶貝,她有一個(gè)神奇的 $ 函數(shù),可以讓我快速選中某一個(gè)或一組 DOM 元素,并提供鏈?zhǔn)秸{(diào)用以減少代碼的冗余。雖然現(xiàn)在提到 jQuery 這個(gè)名詞,你會(huì)覺得老土,“9102 都快過去了你跟我說 Nokia?”。土歸土,但也是真的香。盡管這幾年風(fēng)生水起的 Vue、React 加劇了 jQuery 的沒落,但全世界仍有超過 6600 萬個(gè)網(wǎng)站在使用 jQuery,占全球所有網(wǎng)站數(shù)量的 74%。
jQuery 也給業(yè)界留下了產(chǎn)生深遠(yuǎn)影響的“遺產(chǎn)”,W3C 就仿照其 $ 函數(shù)實(shí)現(xiàn)了 querySelector 和 querySelectorAll。而諷刺的是,也正是這兩個(gè)原生方法的出現(xiàn),大大加快了 jQuery 的沒落,因?yàn)樗鼈內(nèi)〈饲罢咦畛S玫墓δ?—— 快捷的選擇 DOM 元素。
雖然這兩個(gè)新方法寫起來有點(diǎn)長(問題不大,封裝一哈),但是它們是真的賊好用。來,沖!
獲取單個(gè) DOM 元素
向 document.querySelector 中傳入任何有效的 css 選擇器,即可選中單個(gè) DOM 元素:
如果頁面上沒有指定的元素時(shí),返回 null。
獲取 DOM 元素集合
使用 document.querySelectorAll 可以獲取一個(gè)元素集合,它的傳參和 document.querySelector 一毛一樣。它會(huì)返回一個(gè)靜態(tài)的 NodeList ,如果沒有元素被查找到,則會(huì)返回一個(gè)空的 NodeList 。
NodeList 是一個(gè)可遍歷的對象(aka:偽數(shù)組),雖然和數(shù)組很像,但它確實(shí)不是數(shù)組,雖然可以利用 forEach 遍歷它,但它并不具備數(shù)組的一些方法,比如 map、reduce、find。
那么問題來了,如何將一個(gè)偽數(shù)組轉(zhuǎn)化為數(shù)組呢?ES6 為開發(fā)者提供了兩個(gè)便利的選擇
遠(yuǎn)古時(shí)代,開發(fā)者們常用 getElementsByTagName 和 getElementsByClassName 去獲取元素集合,但不同于 querySelectorAll,它們獲取的是一個(gè)動(dòng)態(tài)的 HTMLCollection,這就意味著,它的結(jié)果會(huì)一直隨著 DOM 的改變而改變。
元素的局部搜索
當(dāng)需要查找元素時(shí),不一定每次都基于 document 去查找。開發(fā)者可以在任何 HTMLElement 上進(jìn)行 DOM 元素的局部搜索:
事實(shí)證明,每個(gè)優(yōu)秀的開發(fā)者都是很懶的。為了減少對寶貝鍵盤的損耗,我一般會(huì)這么干:
保護(hù)機(jī)械鍵盤,從我做起。
少年,爬上這棵 DOM 樹
上述內(nèi)容的主題是查找 DOM 元素,這是一個(gè)自上而下的過程:從父元素向其包含的子元素發(fā)起查詢。
但沒有一個(gè) API 可以幫助開發(fā)者借由子元素向父元素發(fā)起查詢。
迷惑之際,MDN 給我提供了一個(gè)寶藏方法:closest
Starting with the Element itself, the closest method traverses parents (heading toward the document root) of the Element until it finds a node that matches the provided selectorString. Will return itself or the matching ancestor. If no such element exists, it returns null.
也就是說,closest 方法可以從特定的 HTMLElement 向上發(fā)起查詢,找到第一個(gè)符合指定 css 表達(dá)式的父元素(也可以是元素自身),如果找到了文檔根節(jié)點(diǎn)還沒有找到目標(biāo)時(shí),就會(huì)返回 null 。
添加 DOM 元素
如果用原生 Java 向 DOM 中添加一個(gè)或多個(gè)元素,一般開發(fā)者的內(nèi)心都是抗拒的,為啥呢?假設(shè)向頁面添加一個(gè) a 標(biāo)簽:
正常情況下,需要寫出如下的代碼:
真的麻煩。
而老大哥 jQuery 可以簡化為:
但,各位觀眾,如今原生 Java 也可以實(shí)現(xiàn)這一操作了:
這個(gè)方法允許你將任何有效的 HTML 字符串插入到一個(gè) DOM 元素的四個(gè)位置,這四個(gè)位置由方法的第一個(gè)參數(shù)指定,分別是:
'beforebegin':元素之前
'afterbegin':元素內(nèi),位于現(xiàn)存的第一個(gè)子元素之前
'beforeend':元素內(nèi),位于現(xiàn)存的最后一個(gè)子元素之后
'afterend': 元素之后
舒服了呀。
更舒服的是,它還有兩個(gè)好兄弟,讓開發(fā)者可以快速地插入 HTML 元素和字符串:
移動(dòng) DOM 元素
上面提到的兄弟方法 insertAdjacentElement 也可以用來對已存在的元素進(jìn)行移動(dòng),換句話說:當(dāng)傳入該方法的是已存在于文檔中的元素時(shí),該元素僅僅只會(huì)被移動(dòng)(而不是復(fù)制并移動(dòng))。
如果你有以下 HTML:
然后操作一下,把
搞到
的后面去:
于是我們就得到了這樣的結(jié)果:
替換 DOM 元素
replaceChild? 這是幾年前的做法了,每當(dāng)開發(fā)者需要替換兩個(gè) DOM 元素,除了需要拿到這必須的兩個(gè)元素之外,還需要獲取他們的直接父元素:
而如今,開發(fā)者們可以使用 replaceWith 就可以完成兩個(gè)元素之間的替換了:
從用法上來說,要比前者清爽一些。
需要注意的是:
? 如果傳入的 newElement 已經(jīng)存在于文檔中,那么方法的執(zhí)行結(jié)果將是 newElement 被移動(dòng)并替換掉 oldElement
?如果傳入的 newElement 是一個(gè)字符串,那么它將作為一個(gè) TextNode 替換掉原有的元素
移除 DOM 元素
和替換元素的老方法相同,移除元素的老方法同樣需要獲取到目標(biāo)元素的直接父元素:
現(xiàn)在只需要在目標(biāo)元素上執(zhí)行一次 remove 方法就 ok 了:
用 HTML 字符串創(chuàng)建 DOM 元素
細(xì)心的你一定發(fā)現(xiàn)了,上文提到的 insertAdjacent 方法允許開發(fā)者直接將一段 HTML 插入到文檔當(dāng)中,如果我們此刻只想生成一個(gè) DOM 元素以備將來使用呢?
DOMParser 對象的 parseFromString 方法即可滿足這樣的需求。該方法可以實(shí)現(xiàn)將一串 HTML 或 XML 字符串轉(zhuǎn)化為一個(gè)完整的 DOM 文檔,也就是說,當(dāng)我們需要獲得預(yù)期的 DOM 元素時(shí),需要從方法返回的 DOM 文檔中獲取這個(gè)元素:
做一個(gè)檢查 DOM 的小能手
標(biāo)準(zhǔn)的 DOM API 為開發(fā)者們提供了很多便利的方法去檢查 DOM 。比如,matches 方法可以判斷出一個(gè)元素是否匹配一個(gè)確定的選擇器:
contains 方法可以檢測出一個(gè)元素是否包含另一個(gè)元素(或者:一個(gè)元素是否是另一個(gè)元素的子元素):
判斷兩個(gè)元素的位置關(guān)系
compareDocumentPosition 是一個(gè)強(qiáng)大的 API ,它可以快速判斷出兩個(gè) DOM 元素的位置關(guān)系,諸如:先于、跟隨、是否包含。它返回一個(gè)整數(shù),代表了兩個(gè)元素之間的關(guān)系。
標(biāo)準(zhǔn)語句:
返回值定義如下:
1: 兩個(gè)元素不在同一個(gè)文檔內(nèi)
2: otherElement 在 element 之前
4: otherElement 在 element 之后
8: otherElement 包含 element
16: otherElement 被 element 所包含
那么問題來了,為什么上面例子中第一行的結(jié)果是20、第二行的結(jié)果是10呢?
因?yàn)?h1 同時(shí)滿足“被 container 所包含(16)” 和 “在 container 之后”,所以語句的執(zhí)行結(jié)果是 16+4=20,同理可推出第二條語句的結(jié)果是 8+2=10。
DOM 觀察者: Mutation Observer
在處理用戶交互的時(shí)候,當(dāng)前頁面的 DOM 元素通常會(huì)發(fā)生很多變化,而有些場景需要開發(fā)者們監(jiān)聽這些變化并在觸發(fā)后執(zhí)行相應(yīng)的操作。MutationObserver 是瀏覽器提供的一個(gè)專門用來監(jiān)聽 DOM 變化的接口,它強(qiáng)大到幾乎可以觀測到一個(gè)元素的所有變化,可觀測的對象包括:文本的改變、子節(jié)點(diǎn)的添加和移除和任何元素屬性的變化。
如同往常一樣,如果想構(gòu)造任何一個(gè)對象,那就 new 它的構(gòu)造函數(shù):
傳入構(gòu)造函數(shù)的是一個(gè)回調(diào)函數(shù),它會(huì)在被監(jiān)聽的 DOM 元素發(fā)生改變時(shí)執(zhí)行,它的兩個(gè)參數(shù)分別是:包含本次所有變更的列表 MutationRecords 和 observer 本身。其中,MutationRecords 的每一條都是一個(gè)變更記錄,它是一個(gè)普通的對象,包含如下常用屬性:
type: 變更的類型,attributes / characterData / childList
target: 發(fā)生變更的 DOM 元素
addedNodes: 新增子元素組成的 NodeList
removedNodes: 已移除子元素組成的的 NodeList
attributeName: 值發(fā)生改變的屬性名,如果不是屬性變更,則返回 null
previousSibling: 被添加或移除的子元素之前的兄弟節(jié)點(diǎn)
nextSibling: 被添加或移除的子元素之后的兄弟節(jié)點(diǎn)
根據(jù)目前的信息,可以寫一個(gè) callback 函數(shù)了:
至此,我們有了一個(gè) DOM 觀察者 observer,也有了一個(gè)完整可用的 DOM 變化后的回調(diào)函數(shù) callback,就差一個(gè)需要被觀測的 DOM 元素了:
在上面的代碼中,我們通過調(diào)用觀察者對象的 observe 方法,對 id 為 target 的 DOM 元素進(jìn)行了觀測(第一個(gè)參數(shù)就是需要觀測的目標(biāo)元素),而第二個(gè)元素,我們傳入了一個(gè)配置對象:開啟對屬性的觀測 / 只觀測 class 屬性 / 屬性變化時(shí)傳遞屬性舊值 / 開啟對子元素列表的觀測。
配置對象支持如下字段:
attributes: Boolean,是否監(jiān)聽元素屬性的變化
attributeFilter: String[],需要監(jiān)聽的特定屬性名稱組成的數(shù)組
attributeOldValue: Boolean,當(dāng)監(jiān)聽元素的屬性發(fā)生變化時(shí),是否記錄并傳遞屬性的上一個(gè)值
characterData: Boolean,是否監(jiān)聽目標(biāo)元素或子元素樹中節(jié)點(diǎn)所包含的字符數(shù)據(jù)的變化
characterDataOldValue: Boolean,字符數(shù)據(jù)發(fā)生變化時(shí),是否記錄并傳遞其上一個(gè)值
childList: Boolean,是否監(jiān)聽目標(biāo)元素添加或刪除子元素
subtree: Boolean,是否擴(kuò)展監(jiān)視范圍到目標(biāo)元素下的整個(gè)子樹的所有元素
當(dāng)不再監(jiān)聽目標(biāo)元素的變化時(shí),調(diào)用 observer 的 disconnect 方法即可,如果需要的話,可以先調(diào)用 observer 的 takeRecords 方法從 observer 的通知隊(duì)列中刪除所有待處理的通知,并將它們返回到一個(gè)由 MutationRecord 對象組成的數(shù)組當(dāng)中:
The End
盡管大部分 DOM API 的名字都很長(寫起來很麻煩),但它們都是非常強(qiáng)大并且通用的。這些 API 往往旨在為開發(fā)者提供底層的構(gòu)建單元,以便在此之上建立更為通用和簡潔的抽象邏輯,因此從這個(gè)角度出發(fā),它們必須提供一個(gè)完整的名稱以變得足夠明確和清晰。
只要能發(fā)揮出這些 API 本應(yīng)該發(fā)揮出的潛能,多敲幾下鍵盤又何妨呢?
DOM 是每個(gè) Javs 開發(fā)者必不可少的知識(shí),因?yàn)槲覀儙缀趺刻於荚谑褂盟D?#xff0c;大膽激發(fā)自己操作 DOM 的洪荒之力吧,盡早成為一個(gè) DOM 高級工程師。
本文干貨部分翻譯自: Use the DOM like a Pro
https://itnext.io/using-the-dom-like-a-pro-163a6c552eba?gi=9cacb5510dff
譯者:kyrieliu(劉凱里)
SegmentFault 社區(qū)文章鏈接:
https://segmentfault.com/a/1190000021199145
責(zé)任編輯:
總結(jié)
以上是生活随笔為你收集整理的html dom 高级,DOM 高级工程师不完全指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 宁波大学计算机专业英语面试的形式,宁波大
- 下一篇: html 5 gif手机版,动画GIF在