深刻理解DOM(一)

316 阅读3分钟

在实际使用html时候,经常会被querySelector()、NodeList、HtmlCollection搞混。深入的学习了一下DOM的相关知识。本文中,对曾经的一些疑惑也进行了解答:

  1. DOM1、DOM2、DOM3都是什么?
  2. NodeList、HtmlCollection是什么,有什么区别? ...

DOM定义

DOM:Document Object Model,文档对象模型,是针对HTML和XML文档的一个API。DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。

  • DOM1:表示DOM1级规范,为基本的文档结构及查询提供了接口。

IE中,所有的DOM对象都是以COM对象的形式实现的,所以IE中的DOM对象与原生JavaScript对象的行为会不一致。

Node类型

DOM1定义了一个Node接口,JS中所有节点类型都继承自Node类型。
节点拥有的属性:

属性类型
读取属性nodeType、childNodes、firstChild、lastChild、parentNode、nextSibling、previousSibling
操作属性appendChild、insertBefore、replaceChild、removeChild、cloneNode

每个节点都有一个nodeType属性,用于表明节点的类型,一共有12个常量数值表示,我们这里列举常用的两个:

Node.ELEMENT_NODE(1)
Node.ATTRIBUTE_NODE(2)
Node.TEXT_NODE(3)
Node.DOCUMENT_NODE(9)

读取属性:
每个节点都有一个childNodes属性,其中保存着一个NodeList对象。NodeList对象是个类数组对象,是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中。

demo:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
        <title>dom</title>
    </head>
    <body>
        <div id="parent">
            parent
            <div id="child1">child1</div>
            <div id="child2">child2</div>
        </div>
        <script>
            let parentEle = document.getElementById('parent');
            console.log(parentEle.nodeType === Node.ELEMENT_NODE) // true
            console.log(parentEle.nodeType === 1)  // true
            // nodeName的值是元素的标签名
            console.log(parentEle.nodeName)  // DIV 多数时候使用tagName
            console.log(parentEle.nodeValue)  // null
            console.log(parentEle.childNodes[0]) // " parent "
            console.log(parentEle.childNodes[0].nodeType) // 3
            console.log('parentEle.childNodes', parentEle.childNodes)
            console.log('parentEle.firstChild', parentEle.firstChild)
            console.log(parentEle.firstChild === parentEle.childNodes[0]) // true
            console.log(parentEle.lastChild === parentEle.childNodes[parentEle.childNodes.length - 1]) // true
            
            let child1Ele = document.getElementById('child1');
            console.log('child1Ele.parentNode', child1Ele.parentNode)
            console.log('child1Ele.nextSibling', child1Ele.nextSibling)
            console.log('child1Ele.previousSibling', child1Ele.previousSibling)
        </script>
    </body>
</html>

image.png

操作属性:

<body>
    <div id="active">
        active
    </div>
    <div id="parent">
        parent
        <div id="child1">child1</div>
        <div id="child2">child2</div>
    </div>
    <script>
        let parentEle = document.getElementById('parent');
        let activeNode = document.getElementById('active');
        // appendChild(): 向 childNodes 列表的末尾添加一个节点
        const returnedNode = parentEle.appendChild(activeNode);
        console.log('returnedNode',returnedNode)
        console.log(returnedNode == activeNode) // true
    </script>
</body>

观察dom的节点变化,就可以理解为什么说NodeList是动态的。 image.png

// 插入后成为最后一个节点
someNode.insertBefore(newNode, null)
// 插入后成为第一个节点
someNode.insertBefore(newNode, firstChild)
<body>
    <div id="parent">
        parent
        <div id="child1">child1</div>
        <div id="child2">child2</div>
    </div>
    <script>
        const parentEle = document.getElementById('parent');
        const child1Ele = document.getElementById('child1');
        const newNode = document.createElement('div');
        newNode.setAttribute('id', 'newId');
        newNode.innerText = "newText";
        console.log(newNode)
        const replaceNode = parentEle.replaceChild(newNode, child1Ele)
        console.log('replaceNode',replaceNode)
        console.log(replaceNode == child1Ele) // true
    </script>
</body>

image.png

const child2Ele = document.getElementById('child2');
const remodeNode = parentEle.removeChild(child2Ele);
console.log(remodeNode == child1Ele) // true

Document类型

Document类型表示文档。document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面,是window对象的一个属性,可以作为全局对象来访问。
nodeType: 9
nodeName: "#document"
nodeValue: null
parentNode: null

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
        <title>dom</title>
    </head>
    <body>
        <script>
            console.log(document.documentElement)  // 获取html标签
            console.log(document.childNodes[0])
            console.log(document.childNodes[1] == document.documentElement) // true
            console.log(document.body)  // 获取body标签
        </script>
    </body>
</html>

image.png

文档信息
获取/修改title:document.title
网页请求:url、domain、referrer;只有domain可以设置,通过设置内嵌页面的domain可以解决跨域问题。在没有来源页面的情况下,referrer可能为空字符串。这些信息都存在于http请求头。

HTMLCollection

DOM节点(node)不光包含HTML元素,还包含text node(字符节点)和comment(注释),既然HTMLCollection只包含HTML元素,那NodeList包含所有类型的DOM节点。

HTMLCollection和NodeList区别:

  • NodeList:一个节点的集合,既可以包含元素,又可以包含其他节点(注释节点、文本节点等)
  • HTMLCollection:元素的集合,只有Element
// 返回HTMLCollection对象
document.getElementsByTagName()
document.anchors // 所有带name属性的<a>元素
document.links // 所有带href属性的<a>元素
document.forms == document.getElementByTagsName('form')
document.images == document.getElementByTagsName('img')
document.getElementsName()
document.getElementsByClassName()
// 返回NodeList对象
document.getElementById()
document.querySelector()
document.querySelectorAll()

返回HTMLCollection对象和返回NodeList对象的不同demo:

<p id="testp" class="test-p-class">
    <span>p content</span>
</p>
<script>
    const pLabelEle = document.getElementsByTagName('p');
    const pIdEle = document.getElementById('testp')
    const pClassEle = document.getElementsByClassName('test-p-class')
    console.log('pLabelEle', pLabelEle)
    console.log(pLabelEle.childNodes)

    console.log('pIdEle', pIdEle)
    console.log(pIdEle.childNodes)

    console.log('pIdEle', pClassEle)
    console.log(pClassEle.childNodes)

</script>

结果: image.png


children属性是HTMLCollection的实例,包含元素中还是元素的子节点,其他和childNodes没什么区别。

<div class="parent">
    this is patent content
    <div class="child">
        this is child content
    </div>
    <!-- this is comment -->
</div>
<script>
    var parent = document.querySelector('.parent');
    console.log(parent.childNodes);
    console.log(parent.children);
</script>

输出结果:

image.png

至于parent即有childNodes属性,又有children属性呢?

因为parent即是一个Node对象(拥有childNodes属性),又因为它有子元素所以它又是一个ParentNode对象(拥有children属性)。


HTMLCollection是比较早期的模型,只能包含HTML元素,早期就有的接口如document.getElementsByClassNamedocument.getElementsByTagName返回的就是HTMLCollection。NodeList是比较新的模型,相比HTMLCollection更加完善,不光有HTML元素,还有text节点和comment。比较新的接口如document.querySelectorAll返回的就是NodeList。

详解NodeList和HtmlCollection:segmentfault.com/a/119000001…