DOM概览

148 阅读4分钟

1. DOM(Document Object Model)文档对象模型

JavaScript中,将网页视为树结构

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>
        <p>文字1</p>
        <span>文字2</span>
    </div>
    <p>
        <span>文字3</span>
        文字4
    </p>
</body>
</html>

JavaScript是通过document操作“这棵树”,document是浏览器加上的对象

2. 获取元素的方式

  • window.idName或者直接用idName

  • document.getElementById('a') 获取ID为a的元素,只返回一个

  • document.getElementsByName('a') 获取所有name属性值为a的元素,返回一个在 (X)HTML document的节点列表集合

  • document.getElementsByTagName('p') 返回一个包括所有给定标签名称的元素的HTML集合HTMLCollection。 整个文件结构都会被搜索,包括根节点。返回的 HTML集合是动态的, 意味着它可以自动更新自己来保持和 DOM 树的同步而不用再次调用 document.getElementsByTagName()

  • document.getElementsByClassName('a') 返回一个包含了所有指定类名的子元素的类数组对象。当在document对象上调用时,会搜索整个DOM文档,包含根节点。你也可以在任意元素上调用getElementsByClassName()方法,它将返回的是以当前元素为根节点,所有指定类名的子元素。

  • document.querySelector(selector) 返回文档中与指定选择器或选择器组匹配的第一个 Element对象。 如果找不到匹配项,则返回null

  • document.querySelectorAll 返回与指定的选择器组匹配的文档中的元素列表 (使用深度优先的先序遍历文档的节点)。返回的对象是 NodeList

    获取特定的元素

  • document.documentElement 获取html元素

  • document.head 获取head元素

  • document.body 获取body元素

  • window 获取窗口(实质不是元素)

  • document.all 获取所有元素(直接判断document.all是false,但是能获取到所有元素)

注意:falsy 新晋成员 document.all (undefined/null/NaN/''/0)

3. 元素原型

以div为例,在chrome中查看一下它的原型

console.dir(div)

具体的链路是:HTMLDivElement---HTMLElement---Element---Node---EventTarget---Function---Object

然后不同的原型上加了各自的一些方法,其中部分方法的功能是一致的,所以在一些DOM操作中,一个动作对应多种写法。

div.png

4. 节点(node)和元素(element)区别

console.log(node.nodeType) 查看节点类型

元素是节点的一个子类型

常见类型描述
元素节点1一个元素 节点,例如

文本节点3Element 或者 Attr中实际的 文字
注释8一个 Comment 节点。
文档9一个 Document 节点。

5. 节点的增删改查

  • 创建一个标签节点 createElement
let box = document.createElement('div')
  • 创建一个文本节点 createTextNode
let text1 = document.createTextNode('你好')
  • 标签里面插入文本 appendChild innerText textContent
box.appendChild(text1)
box.innerText = '你好'
box.textContent('你好')

注意:在JavaScript中通过createElement创建标签,默认是存在于JavaScript线程中,需要通过document.appendChild插入到headbody中才能生效

假设页面中有div#test1div#test2

let div = documet.createElement('div')
test1.appendChild(div)
test2.appendChild(div)

这个新建的div元素,最终会被插入到#test2

  • 删除节点

子节点.parentNode.removeChild(子节点)

子节点.remove()(IE不支持)

注意:删除只是从DOM树中删除,内存中还是有的,所以可以重新添加,删除后,将子节点置为null,就可以完全删除

  • 修改标准属性

    div.className = 'red' 
    

    不能用保留字作为key,所以.class不行,要用.className

    将div对应的标签的类改为red,如果之前有其他类,会被删除

    div.classList.add('red') //div对应标签增加一个red类,不影响之前已有的类
    
    div.style.backgroundColor = 'red'
    
    div.style['background-color'] = 'red'
    
    div.dataset.x = 'xxx' //修改自定义的data-x属性
    
  • 读取标准属性

    div.classList/a.href
    
    div.getAttribute('class')/a.getAttribute('href') 
    
    <a id='test' href='/xxx' >
    

    test.href 的值可能是 域名+/xxx

    稳妥起见,用test.getAttribute('href')获取

  • 修改文本内容

    div.innerText = 'hello'
    
    div.textContent = 'hello'
    
  • 修改HTML内容

    div.innerHTML = '<p>hello world<p>'
    
  • 修改标签

    div.innerHTML = '' //先清空
    div.appendChild(div1) //再加内容
    
  • 修改父节点

    <div id="a">
        <p>11111</p>
    </div>
    <div id="b"></div>
    
    let p = document.querySelector('p')
    b.appendChild(p) //p从a转到了b上
    
  • 查父节点 node.parentNode / node.parentElement

  • 查子元素 node.childNodes / node.children

  • 查兄弟元素 node.parentNode.children 再排除自己

  • 上一个兄弟元素 node.previousSibling

  • 下一个兄弟元素 node.nextSibling

  • 遍历一个div里的所有元素 (递归)

    let div = document.querySelector('div');
    function findAllChildren(ele, fn) {
        fn(ele)
        if (ele.children.length !== 0) {
            for (let i = 0; i < ele.children.length; i++) {
                findAllChildren(ele.children[i], fn)
            }
        }
    }
    findAllChildren(div, (ele) => { console.log(ele) })
    
  • 改写事件处理函数

    div.onclick为例,默认的onclick事件是空,当定义事件操作后,点击div时,浏览器会调用此函数,fn.call(div,event),this指向当前divevent则包含了点击事件的所有信息

6. DOM操作-跨线程

JS引擎操作JS,渲染引擎操作页面

当JS引擎执行到document.body.appendChild(div)时,浏览器就会通知渲染引擎在页面新增一个div元素,并同步属性

bridge.png 插入新标签的大致过程

  • 在div放入(appendChild)页面之前,都属于JS线程内的操作

  • 将div放入页面时,浏览器会通知渲染进程,进行渲染

  • 在div放入页面后,对div的所有操作都有可能会触发重新渲染

属性同步

  • 标准属性

    通过JS修改标签的标准属性,例如,id、class、title等,会被浏览器自动同步到页面,显示的没变,当再次获取或者输出时,会变

  • data-x 自定义属性,通过node.dataset 修改的自定义属性,也会被浏览器同步到页面

  • 非标准属性,修改后不同被同步,所以自定义属性建议使用data-为前缀

拓展阅读:

为什么说DOM慢