[心得] 都2017年了 學學用原生JS來操作DOM吧

作者: jmlntw (吉米林)   2017-04-07 19:08:27
JavaScript 在經過這幾年的進化之後,
原本大家習慣使用第三方函示庫(例如 jQuery)包裝的 DOM 操作方法,
現在都能夠使用原生的 JavaScript 來達成了。
參考:https://www.sitepoint.com/dom-manipulation-vanilla-javascript-no-jquery/
【一、查詢和取得 DOM】
我們有很方便的 querySelector() 和 querySelectorAll() 方式來取得 DOM。
// 取得單一元素
const oneElement = document.querySelector('#foo > div.bar')
// 取得所有符合的元素
const allElements = document.querySelectorAll('.bar')
可以透過 matches() 方式檢查元素是否符合指定的選擇器。
oneElement.matches('div.bar') === true
也可以在特定的元素底下繼續查詢。
const button = allElements.querySelector('button[type="submit"]')
那以前慣用的 getElementById()、getElementsByTagName() 呢?
當然也可以使用,但是 querySelector 不能動態更新查詢到的元素。
const elementsNew = document.querySelectorAll('div')
const elementsOld = document.getElementsByTagName('div')
// 動態插入一個新的 div
const newDiv = document.createElement('div')
document.body.appendChild(newDiv)
// elementsOld 會拿到 newDiv;elementsNew 則否。
elementsNew.length !== elementsOld.length
把 querySelectorAll() 回傳的 NodeList 轉成 Array 之後,
就能用 forEach() 方式走訪每個元素。
Array.from(allElements).forEach(element => {
// do something...
})
// IE 還不支援 Array.from(),可以用:
Array.prototype.forEach.call(allElements, element => {
// do something...
})
// 更短的寫法:
[].forEach.call(allElements, element => {
// do something...
})
【二、修改 class 和屬性】
要修改元素的 class,可以用方便的 classList 操作。
oneElement.classList.add('baz')
oneElement.classList.remove('baz')
oneElement.classList.toggle('baz')
// 檢查是否有指定的 class
oneElement.classList.contains('baz')
要修改元素的屬性(attribute),直接指定給該元素即可。
// 取得屬性
const oneValue = oneElement.value
// 設定屬性
oneElement.value = 'hello'
// 一口氣設定好多種屬性,用 Object.assign()
Object.assign(oneElement. {
value: 'hello',
id: 'world'
})
// 要刪除屬性,設定成 null 就好
oneElement.value = null
等等,那為何不用 getAttribute()、setAttribute() 和 removeAttribute () 呢?
因為這些方式是直接修改 HTML 的屬性,會導致瀏覽器進行重繪(redraw),
對效能來說是很大的影響(換句話說就是很慢)。
但如果你要修改的屬性真的需要重繪畫面(例如表格的 colspan 屬性等等)時例外。
要修改元素的 CSS 樣式,可以存取 style 物件。
oneElement.style.paddingTop = '2rem'
要取得元素的 CSS 值,可以像上面一樣透過 style 物件,
也可以透過 window.getComputedStyle() 取得實際的值。
window.getComputedStyle(oneElement).getPropertyValue('padding-top')
【三、修改 DOM】
// 在 element1 裡插入一個 element2
element1.appendChild(element2)
// 在 element1 裡的 element3 之前插入一個 element2
element1.insertBefore(element2, element3)
世界上有 insertBefore() 卻沒有 insertAfter(),所以必須繞個圈。
// 在 element1 裡的 element3 「之後」插入一個 element2
element1.insertBefore(element2, element3.nextSibling)
// 不能寫成:
// element1.insertAfter(element2, element3)
// 複製 DOM
const newElement = oneElement.cloneNode()
element1.appendChild(newElement)
// 建立新的 DOM
const newElement = document.createElement('div')
const newTextNode = document.createTextNode('hello world')
// 移除 DOM,需要參照到親元素
parentElement.removeChild(element1)
// 自己移除自己
element1.parentNode.removeChild(element1)
要修改元素的內容,傳統的做法可以用 innerHTML:
oneElement.innerHTML = '<div>
<h1>hello world</h1>
</div>'
更好的做法是使用 DocumentFragment:
const text = document.createTextNode('continue reading...')
const hr = document.createElement('hr')
const fragment = document.createDocumentFragment()
fragment.appendChild(text)
fragment.appendChild(hr)
oneElement.appendChild(fragment)
【四、監聽事件】
JavaScript 最重要的就是監聽(listen)各種事件來觸發程式碼。
我們使用 addEventListener 來監聽事件處理。
oneElement.addEventListener('click', function (event) {
// do something...
})
同時監聽許多元素時,透過 event.target 來取得是哪個元素觸發的。
Array.from(allElements).forEach(element => {
element.addEventListener('change', function (event) {
console.log(event.target.value)
})
})
只想讓事件觸發一次(jQuery 的 once):
oneElement.addEventListener('change', function listener(event) {
console.log(event.type + ' got triggered on ' + this)
this.removeEventListener('change', listener)
})
【五、動畫】
以前習慣用 window.setTimeout() 來做動畫,
現在我們有更好更快的 window.requestAnimationFrame() 了。
const start = window.performance.now()
const duration = 2000
window.requestAnimationFrame(function fadeIn (now) {
const progress = now - start
oneElement.style.opacity = progress / duration
if (progress < duration) {
window.requestAnimationFrame(fadeIn)
}
}
【六、包裝】
最後我們可以把這些方式全部包在一個 function 裡。
就像 jQuery 一樣,還可以鍊式呼叫(chainable)
(例如: $('foo').css({color: 'red'}).on('click', () => {}) )
const $ = function $(selector, context = document) {
const elements = Array.from(context.querySelectorAll(selector))
return {
elements,
html (newHtml) {
this.elements.forEach(element => {
element.innerHTML = newHtml
})
return this
},
css (newCss) {
this.elements.forEach(element => {
Object.assign(element.style, newCss)
})
return this
},
on (event, handler, options) {
this.elements.forEach(element => {
element.addEventListener(event, handler, options)
})
return this
}
// etc.
}
}
或者用 ES6 的 Class 來包裝:
class DOM {
constructor(selector) {
const elements = document.querySelectorAll(selector)
this.length = elements.length
Object.assign(this, elements)
}
each(callback) {
for (let el of Array.from(this)) {
callback.call(el)
}
return this
}
addClass(className) {
return this.each(function () {
this.classList.add(className)
})
}
removeClass(className) {
return this.each(function () {
this.classList.remove(className)
})
}
hasClass(className) {
return this[0].classList.contains(className)
}
on(event, callback) {
return this.each(function () {
this.addEventListener(event, callback, false)
})
}
// etc.
}
作者: xdraculax (首席怪叔叔)   2017-04-07 19:31:00
尸口巾
作者: hijkxyzuw (i,j,k) ×(x,y,z)   2017-04-07 23:40:00
上色好認真…還有你最後都包成 $ 了,那直接引用 jQuery 不就好了?還幫你處理了兼容問題
作者: Kenqr (function(){})()   2017-04-08 00:43:00
因為要多載入一個library 相容性問題現在也越來越少了http://youmightnotneedjquery.com/https://github.com/oneuijs/You-Dont-Need-jQuery
作者: a42006310 (心如滄海)   2017-04-08 11:57:00
用心推 超讚
作者: Sunal (SSSSSSSSSSSSSSSSSSSSSSS)   2017-04-08 12:49:00
推 但是客戶死不轉換至IE11..繼續用$$$$$
作者: hoyunxian (WildDagger)   2017-04-08 13:35:00
看來也得等所有舊的瀏覽器都被淘汰了才能直接用......
作者: ian90911 (xopowo)   2017-04-08 13:50:00
作者: jmlntw (吉米林)   2017-04-08 14:37:00
在不用 ES6 的情況下,現在官方主流支援中的瀏覽器幾乎都能使用。如果是特定環境(例如瀏覽器擴充功能或 Electron等)那就更不是問題了。當然和用 jQuery 比起來像是在重複造輪子,不過當作熟悉原生 JS 的學習也不是不行。
作者: jiaming (假命)   2017-04-08 14:45:00
推一個~太用心了
作者: kurtisgod (蹦蹦蹦)   2017-04-08 15:34:00
好文推!!!!
作者: lucky1lk (賭到沒錢的人)   2017-04-09 11:02:00
好文推 原生的相當重要
作者: shadowjohn (轉角遇到愛)   2017-04-09 12:08:00
客戶還在ie8,連es6都併進來用了已眼神死,不在乎在幾十k的js...
作者: KNightING   2017-04-09 23:59:00
推用心
作者: tamsky (低調的極限成為注意焦點)   2017-04-10 10:00:00
推用心, 請問有網頁版嗎
作者: Neisseria (Neisseria)   2017-04-10 11:39:00
想到 IE,還是回頭乖乖用 jQuery (煙)不過大大寫得不錯,建議可以弄個網頁,造福人群
作者: jmlntw (吉米林)   2017-04-10 22:07:00
PTT 有網頁版啊。
作者: kiki1503 (琦琦壹伍零參)   2017-04-14 00:21:00
作者: a0960330 (ViperLiu)   2017-04-16 22:52:00
推~VanillaJS
作者: tonylioa   2017-05-04 08:02:00
推用心

Links booklink

Contact Us: admin [ a t ] ucptt.com