掌控 DOM - 它不像你想的那么难

很多开发者认为 DOM 是真的很难(或者很慢)以至于你需要一个巨大的框架才能让它服帖。于是他们投入了大量的时间去学习这些框架。一两年过去了,又一个框架火了于是你不得不扔掉之前的框架从头学起。这样的事情多重复几次就能让你对 JavaScript 产生疲劳,更不用说那多的数不过来的依赖。

如果我告诉你 DOM 其实没那么复杂,你信吗?

DOM 并不难并且它也不慢。

创建元素

要创建一个元素,你只需要写 document.createElement(tagName)

const h1 = document.createElement('h1')

// <h1></h1>

修改文本内容

HTML 元素如果没有任何内容,那就是空的,让我们用 element.textContent 来增加一些文本

h1.textContent = 'Hello world!'

// <h1>Hello world!</h1>

属性

要定义一个 HTML 元素的属性,你可以用 element.setAttribute(name, value)

h1.setAttribute('class', 'hello')

// <h1 class="hello">Hello world!</h1>

若要管理 class 有一个 element.className 属性

h1.className = 'hello'

// <h1 class="hello">Hello world!</h1>

但是,最好的方式是使用 classList

h1.classList.add('hello')

// <h1 class="hello">Hello world!</h1>

h1.classList.remove('hello')

// <h1>Hello world!</h1>

要设置一个元素的 ID,你可以使用标签属性或者 id 属性

h1.setAttribute('id', 'hello-world')

h1.id = 'hello-world'

// <h1 id="hello-world" class="hello">Hello world!</h1>

如果你不确定用标签属性(attributes)还是对象属性(properties),就用标签属性 (表单元素的状态除外,如 value 和 checked)

注意,有些布尔值的修改不能使用 element.setAttribute(someBoolean, false),下面这些则可以

input.checked = true

// <input checked>

input.checked = false

// <input>

input.setAttribute(‘checked’, ‘’)

// <input checked>

input.removeAttribute('checked')  
// <input>

元素附加

HTML 是结构化的。可以通过 parent.appendChild(child) 来实现元素附加

document.body.appendChild(h1)

// <body><h1>Hello world!</h1></body>

元素移除

有时候你希望去掉一个元素,那么你可以使用 parent.removeChild(child)

document.body.removeChild(h1)

// <body></body>

元素查找

你可以使用下列方法来查找子元素

  • document.getElementById(id)
  • element.childNodes[i]
  • element.firstChild = element.childNodes[0]
  • element.lastChild = element.childNodes[element.childNodes.length - 1]
  • element.getElementsByTagName(tagName)
  • element.getElementsByClassName(className)
  • element.querySelector(query)
  • element.querySelectorAll(query)

注意 getElementsByTagName, getElementsByClassName 和 querySelectorAll 返回的不是数组,而是 NodeList,你不能通过 ES5 的数组快速访问方式来迭代。

元素间插入元素

想要将元素查到另一个元素的前面?试试 parent.insertBefore(child, before)

/*

 *  <body>

 *    <script src="main.js"></script>

 *  </body>

 */

document.body.insertBefore(h1, document.body.firstChild)

/*  <body>

 *    <h1>Hello world!</h1>

 *    <script src="main.js"></script>

 *  </body>
 */

创建元素列表

如果我们有一些数据,可以很容易的动态创建元素。

const data = [  
  [ 1, 2, 3 ],
  [ 4, 5, 6 ],
  [ 7, 8, 9 ]
]
const table = document.createElement('table')

data.forEach(row => {  
  const tr = document.createElement('tr')

  row.forEach(cell => {
    const td = document.createElement('td')

    td.textContent = cell
    tr.appendChild(td)
 })

  table.appendChild(tr)
})

document.body.appendChild(table)  

https://jsfiddle.net/pakastin/pkb1yww7/

更新元素列表

你希望元素保持最新状态,可以这么做

const table = document.createElement('table')

document.body.appendChild(table)

updateTable(table, [  
  [ 1, 2 ],
  [ 3, 4, 5 ],
  [ 6, 7, 8, 9 ]
])

setTimeout(() => {  
  updateTable(table, [
    [ 1, 2, 3, 4 ],
    [ 5, 6, 7 ],
    [ 8, 9 ]
  ])
}, 1000)

function updateTable (table, data) {  
  const rowLookup = table._lookup || (table._lookup = [])

  setChildren(table, updateRows(rowLookup, data))
}

function updateRows (rowLookup, rows) {  
     return rows.map((row, y) => {
    const tr = rowLookup[y] || (rowLookup[y] = document.createElement('tr'))
    const cellLookup = tr._lookup || (tr._lookup = [])

    setChildren(tr, updateCells(cellLookup, row))

    return tr
  })
}

function updateCells (cellLookup, cells) {  
     return cells.map((cell, x) => {
    const td = cellLookup[x] || (cellLookup[x] = document.createElement('td'))

    td.textContent = cell

    return td
  })
}

function setChildren (parent, children) {  
  let traverse = parent.firstChild

  for (let i = 0; i < children.length; i++) {
    const child = children[i]

    if (child == null) {
      return
    }

    if (child === traverse) {
      traverse = traverse.nextSibling
    } else if (traverse) {
      parent.insertBefore(child, traverse)
    } else {
      parent.appendChild(child)
    }
  }

  while (traverse) {
    const next = traverse.nextSibling

    parent.removeChild(traverse)

    traverse = next
  }
}

https://jsfiddle.net/pakastin/xk5bt9p8/

以上代码有两个事情发生 1. 一个隐藏属性 element._lookup = [] 用来查找子元素,使用lookup 我们可以复用DOM中已经存在的元素并更新它们。
2. setChildren(parent, children) 方法能让你提供一个元素列表。它很聪明的和已经附加在 parent的元素进行比较并按需进行 插入 移除 重排等操作

tieshou wang

Read more posts by this author.

Subscribe to 王铁手的博客

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!