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

看板Ajax作者 (吉米林)時間7年前 (2017/04/07 19:08), 7年前編輯推噓18(18012)
留言30則, 19人參與, 最新討論串1/2 (看更多)
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. } -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 36.224.11.65 ※ 文章網址: https://www.ptt.cc/bbs/Ajax/M.1491563311.A.1F2.html ※ 編輯: jmlntw (36.224.11.65), 04/07/2017 19:14:00 ※ jmlntw:轉錄至看板 Web_Design 04/07 19:15 ※ 編輯: jmlntw (36.224.11.65), 04/07/2017 19:30:14

04/07 19:31, , 1F
尸口巾
04/07 19:31, 1F

04/07 23:40, , 2F
上色好認真…
04/07 23:40, 2F

04/07 23:41, , 3F
還有你最後都包成 $ 了,那直接引用 jQuery 不就好了?
04/07 23:41, 3F

04/07 23:41, , 4F
還幫你處理了兼容問題
04/07 23:41, 4F

04/08 00:43, , 5F
因為要多載入一個library 相容性問題現在也越來越少了
04/08 00:43, 5F

04/08 00:43, , 6F


04/08 11:57, , 8F
用心推 超讚
04/08 11:57, 8F

04/08 12:49, , 9F
推 但是客戶死不轉換至IE11..繼續用$$$$$
04/08 12:49, 9F

04/08 13:35, , 10F
看來也得等所有舊的瀏覽器都被淘汰了才能直接用......
04/08 13:35, 10F

04/08 13:50, , 11F
04/08 13:50, 11F

04/08 14:37, , 12F
在不用 ES6 的情況下,現在官方主流支援中的瀏覽器幾乎都
04/08 14:37, 12F

04/08 14:37, , 13F
能使用。如果是特定環境(例如瀏覽器擴充功能或 Electron
04/08 14:37, 13F

04/08 14:37, , 14F
等)那就更不是問題了。
04/08 14:37, 14F

04/08 14:44, , 15F
當然和用 jQuery 比起來像是在重複造輪子,不過當作熟悉原
04/08 14:44, 15F

04/08 14:44, , 16F
生 JS 的學習也不是不行。
04/08 14:44, 16F

04/08 14:45, , 17F
推一個~太用心了
04/08 14:45, 17F

04/08 15:34, , 18F
好文推!!!!
04/08 15:34, 18F

04/09 11:02, , 19F
好文推 原生的相當重要
04/09 11:02, 19F

04/09 12:08, , 20F
客戶還在ie8,連es6都併進來用了
04/09 12:08, 20F

04/09 12:08, , 21F
已眼神死,不在乎在幾十k的js...
04/09 12:08, 21F

04/09 23:59, , 22F
推用心
04/09 23:59, 22F

04/10 10:00, , 23F
推用心, 請問有網頁版嗎
04/10 10:00, 23F

04/10 11:39, , 24F
想到 IE,還是回頭乖乖用 jQuery (煙)
04/10 11:39, 24F

04/10 11:40, , 25F
不過大大寫得不錯,建議可以弄個網頁,造福人群
04/10 11:40, 25F

04/10 22:07, , 26F
PTT 有網頁版啊。
04/10 22:07, 26F

04/14 00:21, , 27F
04/14 00:21, 27F

04/16 22:52, , 28F
推~VanillaJS
04/16 22:52, 28F

05/04 08:02, , 29F
推用心
05/04 08:02, 29F

12/05 12:05, , 30F
12/05 12:05, 30F
文章代碼(AID): #1OvtCl7o (Ajax)
文章代碼(AID): #1OvtCl7o (Ajax)