顯示廣告
隱藏 ✕
※ 本文為 mesak 轉寄自 ptt.cc 更新時間: 2017-04-15 02:47:16
看板 Ajax
作者 jmlntw (吉米林)
標題 [心得] 都2017年了  學學用原生JS來操作DOM吧
時間 Fri Apr  7 19:08:27 2017


JavaScript 在經過這幾年的進化之後,
原本大家習慣使用第三方函示庫(例如 jQuery)包裝的 DOM 操作方法,
現在都能夠使用原生的 JavaScript 來達成了。

參考:https://www.sitepoint.com/dom-manipulation-vanilla-javascript-no-jquery/
The Basics of DOM Manipulation in Vanilla JavaScript (No jQuery) — SitePoint
[圖]
[圖]
Sebastian Seitz gives you a crash course in DOM manipulation with vanilla JavaScript, abstracting the more verbose parts into a set of helper function ...

 


【一、查詢和取得 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
※ 文章代碼(AID): #1OvtCl7o (Ajax)
※ 文章網址: 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
xdraculax: 尸口巾1F 04/07 19:31
hijkxyzuw:  上色好認真…2F 04/07 23:40
hijkxyzuw: 還有你最後都包成 $ 了,那直接引用 jQuery 不就好了?
hijkxyzuw: 還幫你處理了兼容問題
Kenqr: 因為要多載入一個library 相容性問題現在也越來越少了5F 04/08 00:43
Kenqr: http://youmightnotneedjquery.com/
You Might Not Need jQuery
Examples of how to do common event, element, ajax and utility operations with plain javascript. ...

 
GitHub - oneuijs/You-Dont-Need-jQuery: Examples of how to do query, style, dom, ajax, event etc like jQuery with plain javascript.
[圖]
You-Dont-Need-jQuery - Examples of how to do query, style, dom, ajax, event etc like jQuery with plain javascript. ...

 
a42006310: 用心推 超讚8F 04/08 11:57
Sunal: 推  但是客戶死不轉換至IE11..繼續用$$$$$9F 04/08 12:49
hoyunxian: 看來也得等所有舊的瀏覽器都被淘汰了才能直接用......10F 04/08 13:35
ian90911: 推11F 04/08 13:50
jmlntw: 在不用 ES6 的情況下,現在官方主流支援中的瀏覽器幾乎都12F 04/08 14:37
jmlntw: 能使用。如果是特定環境(例如瀏覽器擴充功能或 Electron
jmlntw: 等)那就更不是問題了。
jmlntw: 當然和用 jQuery 比起來像是在重複造輪子,不過當作熟悉原
jmlntw: 生 JS 的學習也不是不行。
jiaming: 推一個~太用心了17F 04/08 14:45
kurtisgod: 好文推!!!!18F 04/08 15:34
lucky1lk: 好文推 原生的相當重要19F 04/09 11:02
shadowjohn: 客戶還在ie8,連es6都併進來用了20F 04/09 12:08
shadowjohn: 已眼神死,不在乎在幾十k的js...
KNightING: 推用心22F 04/09 23:59
tamsky: 推用心, 請問有網頁版嗎23F 04/10 10:00
Neisseria: 想到 IE,還是回頭乖乖用 jQuery  (煙)24F 04/10 11:39
Neisseria: 不過大大寫得不錯,建議可以弄個網頁,造福人群
jmlntw: PTT 有網頁版啊。26F 04/10 22:07
kiki1503: 推27F 04/14 00:21

--
※ 看板: Mesak 文章推薦值: 0 目前人氣: 0 累積人氣: 562 
※ 本文也出現在看板: dinos terievv
作者 jmlntw 的最新發文:
點此顯示更多發文記錄
分享網址: 複製 已複製
r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇