在框架盛行的年代,還有多少人記得在沒有框架時我們?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野蝇。
這讓我想到了 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)容:
- Node https://developer.mozilla.org/zh-CN/docs/Web/API/Node
- Element https://developer.mozilla.org/zh-CN/docs/Web/API/Element
- Document https://developer.mozilla.org/zh-CN/docs/Web/API/Document
寫個操作 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é)果如圖:
由于在 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é)果如圖:
簡單實現(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价涝。