Skip to main content

DOM Core

  • DOM Level 0.
  • DOM Level 1:
    • DOM Core.
    • DOM XML.
    • DOM HTML.
  • DOM Level 2:
    • DOM2 Core.
    • DOM2 XML.
    • DOM2 HTML.
    • DOM2 Views.
    • DOM2 StyleSheets.
    • DOM2 CSS.
    • DOM2 CSS 2.
    • DOM2 Events.
    • DOM2 UIEvents.
    • DOM2 MouseEvents.
    • DOM2 MutationEvents (Deprecated).
    • DOM2 HTMLEvents.
    • DOM2 Range.
    • DOM2 Traversal.
  • DOM Level 3:
    • DOM3 Core.
    • DOM3 XML.
    • DOM3 Events.
    • DOM3 UIEvents.
    • DOM3 MouseEvents.
    • DOM3 MutationEvents (Deprecated).
    • DOM3 MutationNameEvents.
    • DOM3 TextEvents.
    • DOM3 Load and Save.
    • DOM3 Load and Save Async.
    • DOM3 Validation.
    • DOM3 XPath.
const hasXmlDom = document.implementation.hasFeature('XML', '1.0')
const hasHtmlDom = document.implementation.hasFeature('HTML', '1.0')

DOM Nodes

document.createElement('nodeName')
document.createTextNode('String')

document.getElementById(id)
document.getElementsByName(elementName)
document.getElementsByTagName(tagName)
document.getElementsByClassName(className) // HTML5
document.querySelector(cssSelector) // Selectors API
document.querySelectorAll(cssSelector) // Selectors API

element.getAttribute(attrName) // get default HTML attribute
element.setAttribute(attrName, attrValue)
element.removeAttribute(attrName)

element.compareDocumentPosition(element)
element.contains(element)
element.isSameNode(element) // Same node reference
element.isEqualNode(element) // Same nodeName/nodeValue/attributes/childNodes
element.matches(cssSelector)
element.closest(cssSelector) // Returns closest ancestor matching selector
element.cloneNode()
element.normalize()
element.before(...elements)
element.after(...elements)
element.replaceWith(...elements)
element.remove()

parentElement.hasChildNodes()
parentElement.appendChild(childElement)
parentElement.append(childElements)
parentElement.insertBefore(newChild, targetChild)
parentElement.replaceChild(newChild, targetChild)
parentElement.replaceChildren(children)
parentElement.removeChild(child)
function showAlert(type, message, duration = 3) {
const div = document.createElement('div')
div.className = type
div.appendChild(document.createTextNode(message))
container.insertBefore(div, form)
setTimeout(() => div.remove(), duration * 1000)
}

DOM Node Type

Node 除包括元素结点 (tag) 外, 包括许多其它结点 (甚至空格符视作一个结点), 需借助 nodeType 找出目标结点.

Node TypeNode RepresentationNode NameNode Value
1ELEMENT_NODETag Namenull
2ATTRIBUTE_NODEAttr NameAttr Value
3TEXT_NODE#textText
4CDATA_SECTION_NODE#cdata-sectionCDATA Section
5ENTITY_REFERENCE_NODE
6ENTITY_NODE
8COMMENT_NODE#commentComment
9DOCUMENT_NODE#documentnull
10DOCUMENT_TYPE_NODEhtml/xmlnull
11DOCUMENT_FRAGMENT_NODE#document-fragmentnull
12NOTATION_NODE
const type = node.nodeType
const name = node.nodeName
const value = node.nodeValue

if (someNode.nodeType === Node.ELEMENT_NODE)
alert('Node is an element.')

DOM Attribute Node

const id = element.attributes.getNamedItem('id').nodeValue
const id = element.attributes.id.nodeValue
element.attributes.id.nodeValue = 'someOtherId'
const oldAttr = element.attributes.removeNamedItem('id')
element.attributes.setNamedItem(newAttr)
const attr = document.createAttribute('align')
attr.value = 'left'
element.setAttributeNode(attr)

alert(element.attributes.align.value) // "left"
alert(element.getAttributeNode('align').value) // "left"
alert(element.getAttribute('align')) // "left"

Further reading: DOM properties reflection on HTML attributes.

DOM Text Node

Text node methods:

  • appendData(text): 向节点末尾添加文本 text.
  • deleteData(offset, count): 从位置 offset 开始删除 count 个字符.
  • insertData(offset, text): 在位置 offset 插入 text.
  • replaceData(offset, count, text): 用 text 替换从位置 offset 到 offset + count 的文本.
  • splitText(offset): 在位置 offset 将当前文本节点拆分为两个文本节点.
  • substringData(offset, count): 提取从位置 offset 到 offset + count 的文本.

Normalize text nodes:

const element = document.createElement('div')
element.className = 'message'

const textNode = document.createTextNode('Hello world!')
const anotherTextNode = document.createTextNode('Yippee!')

element.appendChild(textNode)
element.appendChild(anotherTextNode)
document.body.appendChild(element)
alert(element.childNodes.length) // 2

element.normalize()
alert(element.childNodes.length) // 1
alert(element.firstChild.nodeValue) // "Hello world!Yippee!"

Split text nodes:

const element = document.createElement('div')
element.className = 'message'

const textNode = document.createTextNode('Hello world!')
element.appendChild(textNode)
document.body.appendChild(element)

const newNode = element.firstChild.splitText(5)
alert(element.firstChild.nodeValue) // "Hello"
alert(newNode.nodeValue) // " world!"
alert(element.childNodes.length) // 2
TextContent vs InnerText vs InnerHTML
  • textContent:
    • Security: Doesn’t parse HTML.
    • Performance: Including <script> and <style> text content.
  • innerText:
    • Doesn't parse HTML.
    • Only show human-readable text content
    • innerText care CSS styles, read innerText value will trigger reflow.
  • innerHTML:
    • Do parse HTML.
const textContent = element.textContent
const innerHTML = element.innerHTML
// eslint-disable-next-line unicorn/prefer-dom-node-text-content -- API example
const innerText = element.innerText

DOM Document Node

document node (#document):

alert(document.nodeType) // 9
alert(document.nodeName) // "#document"
alert(document.nodeValue) // null
const html = document.documentElement
const doctype = document.doctype
const head = document.head // HTML5 head.
const body = document.body

const title = document.title // 可修改.
const domain = document.domain // 可设置同源域名.
const url = document.URL
const referer = document.referer
const charSet = document.characterSet // HTML5 characterSet.

const anchors = documents.anchors
const images = documents.images
const links = documents.links
const forms = documents.forms
const formElements = documents.forms[0].elements // 第一个表单内的所有字段

// HTML5 compatMode:
if (document.compatMode === 'CSS1Compat')
console.log('Standards mode')
else if (document.compatMode === 'BackCompat')
console.log('Quirks mode')
document.getElementById(id)

document.getElementsByName(name)
document.getElementsByTagName(tagName)
document.getElementsByClassName(className) // HTML5
document.querySelector(cssSelector) // Selectors API
document.querySelectorAll(cssSelector) // Selectors API
document.write()
document.writeln()

DOM Document Type Node

<!DOCTYPE html PUBLIC "-// W3C// DTD HTML 4.01// EN" "http:// www.w3.org/TR/html4/strict.dtd">
console.log(document.doctype.name) // "html"
console.log(document.nodeType) // 10
console.log(document.doctype.nodeName) // "html"
console.log(document.doctype.nodeValue) // null
console.log(document.doctype.publicId) // "-// W3C// DTD HTML 4.01// EN"
console.log(document.doctype.systemId) // "http://www.w3.org/TR/html4/strict.dtd"

const doctype = document.implementation.createDocumentType(
'html',
'-// W3C// DTD HTML 4.01// EN',
'http://www.w3.org/TR/html4/strict.dtd'
)
const doc = document.implementation.createDocument(
'http://www.w3.org/1999/xhtml',
'html',
doctype
)

DOM Document Fragment Node

减少 DOM 操作次数, 减少页面渲染次数:

const frag = document.createDocumentFragment()

let p
let t

p = document.createElement('p')
t = document.createTextNode('first paragraph')
p.appendChild(t)
frag.appendChild(p)

p = document.createElement('p')
t = document.createTextNode('second paragraph')
p.appendChild(t)
frag.appendChild(p)

// 只渲染一次HTML页面
document.body.appendChild(frag)

克隆节点进行处理, 处理完毕后再替换原节点:

const oldNode = document.getElementById('result')
const clone = oldNode.cloneNode(true)
// work with the clone

// when you're done:
oldNode.parentNode.replaceChild(clone, oldNode)

Parse HTML:

const range = document.createRange()
const parse = range.createContextualFragment.bind(range)

parse(`<ol>
<li>a</li>
<li>b</li>
</ol>
<ol>
<li>c</li>
<li>d</li>
</ol>`)

function parseHTML(string) {
const context = document.implementation.createHTMLDocument()

// Set the base href for the created document so any parsed elements with URLs
// are based on the document's URL
const base = context.createElement('base')
base.href = document.location.href
context.head.appendChild(base)

context.body.innerHTML = string
return context.body.children
}

DOM Manipulation

Append DOM Node

MethodNodeHTMLTextIEEvent ListenersSecure
appendYesNoYesNoPreservesYes
appendChildYesNoNoYesPreservesYes
innerHTMLNoYesYesYesLosesCareful
insertAdjacentHTMLNoYesYesYesPreservesCareful
const testDiv = document.getElementById('testDiv')

const para = document.createElement('p')
testDiv.appendChild(para)

const txt = document.createTextNode('Hello World')
para.appendChild(txt)

innerHTML: non-concrete, including all types of childNodes:

div.innerHTML = '<p>Test<em>test</em>Test.</p>'
// <div>
// <p>Test<em>test</em>Test.</p>
// </div>

innerHTML performance:

// BAD
for (const value of values)
ul.innerHTML += `<li>${value}</li>` // 别这样做!

// GOOD
let itemsHtml = ''
for (const value of values)
itemsHtml += `<li>${value}</li>`

ul.innerHTML = itemsHtml

// BEST
ul.innerHTML = values.map(value => `<li>${value}</li>`).join('')

Insert DOM Node

// Append
el.appendChild(newEl)

// Prepend
el.insertBefore(newEl, el.firstChild)

// InsertBefore
el.parentNode.insertBefore(newEl, el)

// InsertAfter
function insertAfter(newElement, targetElement) {
const parent = targetElement.parentNode

if (parent.lastChild === targetElement)
parent.appendChild(newElement)
else
parent.insertBefore(newElement, targetElement.nextSibling)
}

insertAdjacentHTML/insertAdjacentText:

  • beforebegin: 插入前一个兄弟节点.
  • afterbegin: 插入第一个子节点.
  • beforeend: 插入最后一个子节点.
  • afterend: 插入下一个兄弟节点.
// 4 positions:
//
// <!-- beforebegin -->
// <p>
// <!-- afterbegin -->
// foo
// <!-- beforeend -->
// </p>
// <!-- afterend -->
const p = document.querySelector('p')

p.insertAdjacentHTML('beforebegin', '<a></a>')
p.insertAdjacentText('afterbegin', 'foo')

// simply be moved element, not copied element
p.insertAdjacentElement('beforebegin', link)

Replace DOM Node

node.replaceChild(document.createTextNode(text), node.firstChild)
node.replaceChildren(...nodeList)

Remove DOM Node

// 删除第一个子节点
const formerFirstChild = someNode.removeChild(someNode.firstChild)

// 删除最后一个子节点
const formerLastChild = someNode.removeChild(someNode.lastChild)

while (div.firstChild)
div.removeChild(div.firstChild)

// Remove self
el.parentNode.removeChild(el)
el.remove()

Traverse DOM Node

const parent = node.parentNode
const children = node.childNodes
const first = node.firstChild
const last = node.lastChild
const previous = node.previousSibling
const next = node.nextSibling

node.matches(selector)

Element Traversal API: navigation properties listed above refer to all nodes. For instance, in childNodes can see both text nodes, element nodes, and even comment nodes.

const count = el.childElementCount
const parent = el.parentElement
const children = el.children
const first = el.firstElementChild
const last = el.lastElementChild
const previous = el.previousElementSibling
const next = el.nextElementSibling

el.matches(selector)

NodeList is iterable:

const elements = document.querySelectorAll('div')

for (const element of elements)
console.log(element)

Node Iterator:

const div = document.getElementById('div1')
function filter(node) {
return node.tagName.toLowerCase() === 'li'
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP
}
const iterator = document.createNodeIterator(
div,
NodeFilter.SHOW_ELEMENT,
filter,
false
)

for (
let node = iterator.nextNode();
node !== null;
node = iterator.nextNode()
)
console.log(node.tagName) // 输出标签名

Tree Walker:

const div = document.getElementById('div1')
const walker = document.createTreeWalker(
div,
NodeFilter.SHOW_ELEMENT,
null,
false
)

walker.firstChild() // 前往<p>
walker.nextSibling() // 前往<ul>

for (
let node = walker.firstChild();
node !== null;
node = walker.nextSibling()
)
console.log(node.tagName) // 遍历 <li>
NodeIterator vs TreeWalker
  • NodeFilter.acceptNode() FILTER_REJECT:
    • For NodeIterator, this flag is synonymous with FILTER_SKIP.
    • For TreeWalker, child nodes are also rejected.
  • TreeWalker has more methods:
    • firstChild.
    • lastChild.
    • previousSibling.
    • nextSibling.

Attributes DOM Node

HTML attributes 设置对应的 DOM properties 初始值:

alert(div.getAttribute('id')) // "myDiv" default div.id
alert(div.getAttribute('class')) // "bd" default div.class
div.setAttribute('id', 'someOtherId')
div.setAttribute('class', 'ft')
div.removeAttribute('id')
div.removeAttribute('class')

// `data-src`
console.log(el.dataset.src)

Select DOM Node

Range API:

  • startContainer: 范围起点所在的节点 (选区中第一个子节点的父节点).
  • startOffset: 范围起点在 startContainer 中的偏移量.
  • endContainer: 范围终点所在的节点 (选区中最后一个子节点的父节点).
  • endOffset: 范围起点在 startContainer 中的偏移量.
  • commonAncestorContainer: 文档中以 startContainerendContainer 为后代的最深的节点.
  • setStartBefore(refNode): 把范围的起点设置到 refNode 之前, 从而让 refNode 成为选区的第一个子节点.
  • setStartAfter(refNode): 把范围的起点设置到 refNode 之后, 从而将 refNode 排除在选区之外, 让其下一个同胞节点成为选区的第一个子节点.
  • setEndBefore(refNode): 把范围的终点设置到 refNode 之前, 从而将 refNode 排除在选区之外, 让其上一个同胞节点成为选区的最后一个子节点.
  • setEndAfter(refNode): 把范围的终点设置到 refNode 之后, 从而让 refNode 成为选区的最后一个子节点.
  • setStart(refNode, offset).
  • setEnd(refNode, offset).
  • deleteContents(): remove.
  • extractContents(): remove and return.
  • cloneContents(): clone.
  • insertNode(node): 在范围选区的开始位置插入一个节点.
  • surroundContents(node): 插入包含范围的内容.
  • collapse(boolean): 范围折叠.
  • compareBoundaryPoints(Range.HOW, sourceRange): 确定范围之间是否存在公共的边界 (起点或终点).
<!doctype html>
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
</body>
</html>
const p1 = document.getElementById('p1')
const helloNode = p1.firstChild.firstChild
const worldNode = p1.lastChild
const range = document.createRange()

range.setStart(helloNode, 2)
range.setEnd(worldNode, 3)
const fragment1 = range.cloneContents() // clone
const fragment2 = range.extractContents() // remove and return

p1.parentNode.appendChild(fragment1)
p1.parentNode.appendChild(fragment2)
const p1 = document.getElementById('p1')
const helloNode = p1.firstChild.firstChild
const worldNode = p1.lastChild
const range = document.createRange()

const span = document.createElement('span')
span.style.color = 'red'
span.appendChild(document.createTextNode('Inserted text'))

range.setStart(helloNode, 2)
range.setEnd(worldNode, 3)
range.insertNode(span)
// <p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>
const p1 = document.getElementById('p1')
const helloNode = p1.firstChild.firstChild
const worldNode = p1.lastChild
const range = document.createRange()

const span = document.createElement('span')
span.style.backgroundColor = 'yellow'

range.selectNode(helloNode)
range.surroundContents(span)
// <p><b><span style="background-color:yellow">Hello</span></b> world!</p>

DOM Loading

Dynamic Scripts Loading

function loadScript(url) {
const script = document.createElement('script')
script.src = url
script.async = true
document.body.appendChild(script)
}
function loadScriptString(code) {
const script = document.createElement('script')
script.async = true
script.type = 'text/javascript'

try {
script.appendChild(document.createTextNode(code))
} catch (ex) {
script.text = code
}

document.body.appendChild(script)
}
InnerHTML Script

所有现代浏览器中, 通过 innerHTML 属性创建的 <script> 元素永远不会执行.

Dynamic Styles Loading

function loadStyles(url) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.type = 'text/css'
link.href = url

const head = document.getElementsByTagName('head')[0]
head.appendChild(link)
}
function loadStyleString(css) {
const style = document.createElement('style')
style.type = 'text/css'

try {
style.appendChild(document.createTextNode(css))
} catch (ex) {
style.styleSheet.cssText = css
}

const head = document.getElementsByTagName('head')[0]
head.appendChild(style)
}
StyleSheet CSSText
  • 若重用同一个 <style> 元素并设置该属性超过一次, 则可能导致浏览器崩溃.
  • cssText 设置为空字符串也可能导致浏览器崩溃.

Table Manipulation

<table> 元素添加了以下属性和方法:

  • caption: 指向 <caption> 元素的指针 (如果存在).
  • tBodies: 包含 <tbody> 元素的 HTMLCollection.
  • tFoot: 指向 <tfoot> 元素 (如果存在).
  • tHead: 指向 <thead> 元素 (如果存在).
  • rows: 包含表示所有行的 HTMLCollection.
  • createTHead(): 创建 <thead> 元素, 放到表格中, 返回引用.
  • createTFoot(): 创建 <tfoot> 元素, 放到表格中, 返回引用.
  • createCaption(): 创建 <caption> 元素, 放到表格中, 返回引用.
  • deleteTHead(): 删除 <thead> 元素.
  • deleteTFoot(): 删除 <tfoot> 元素.
  • deleteCaption(): 删除 <caption> 元素.
  • deleteRow(pos): 删除给定位置的行.
  • insertRow(pos): 在行集合中给定位置插入一行.

<tbody> 元素添加了以下属性和方法:

  • rows: 包含 <tbody> 元素中所有行的 HTMLCollection.
  • deleteRow(pos): 删除给定位置的行.
  • insertRow(pos): 在行集合中给定位置插入一行, 返回该行的引用.

<tr> 元素添加了以下属性和方法:

  • cells: 包含 <tr> 元素所有表元的 HTMLCollection.
  • deleteCell(pos): 删除给定位置的表元.
  • insertCell(pos): 在表元集合给定位置插入一个表元, 返回该表元的引用.
// 创建表格
const table = document.createElement('table')
table.border = 1
table.width = '100%'

// 创建表体
const tbody = document.createElement('tbody')
table.appendChild(tbody)

// 创建第一行
tbody.insertRow(0)
tbody.rows[0].insertCell(0)
tbody.rows[0].cells[0].appendChild(document.createTextNode('Cell 1, 1'))
tbody.rows[0].insertCell(1)
tbody.rows[0].cells[1].appendChild(document.createTextNode('Cell 2, 1'))

// 创建第二行
tbody.insertRow(1)
tbody.rows[1].insertCell(0)
tbody.rows[1].cells[0].appendChild(document.createTextNode('Cell 1, 2'))
tbody.rows[1].insertCell(1)
tbody.rows[1].cells[1].appendChild(document.createTextNode('Cell 2, 2'))

// 把表格添加到文档主体
document.body.appendChild(table)

Iframe

Attribute
src="https://google.com/"Sets address of the document to embed
srcdoc="<p>Some html</p>"Sets HTML content of the page to show
height="100px"Sets iframe height in pixels
width="100px"Sets iframe width in pixels
name="my-iframe"Sets name of the iframe (used in JavaScript
allow="fullscreen"Sets feature policy for the iframe
referrerpolicy="no-referrer"Sets referrer when fetching iframe content
sandbox="allow-same-origin"Sets restrictions of the iframe
loading="lazy"Lazy loading
<iframe src="https://www.google.com/" height="500px" width="500px"></iframe>
<iframe src="https://platform.twitter.com/widgets/tweet_button.html"></iframe>
<iframe srcdoc="<html><body>App</body></html>"></iframe>
<iframe
sandbox="allow-same-origin allow-top-navigation allow-forms allow-scripts"
src="http://maps.example.com/embedded.html"
></iframe>
const iframeDocument = iframe.contentDocument
const iframeStyles = iframe.contentDocument.querySelectorAll('.css')
iframe.contentWindow.postMessage('message', '*')