DOM结构及优化

本文深入讲解了DOM(文档对象模型)的概念,包括元素节点、文本节点和属性节点,并探讨了DOM操作导致的性能问题及优化策略,如减少DOM访问、使用事件委托和DOMFragment。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DOM结构

1、DOM(Document Object Model,文档对象模型)
文档:html页面
文档对象:页面中的元素
文档对象模型:为了让程序(js)去操作页面中的元素而定义的标准。通过DOM,可访问HTML文档的所有元素
把整个文档看成一棵树,树分支就是节点,同时定义了很多方法来操作这些节点
2、DOM树中常见的DOM节点
在这里插入图片描述
(1)元素节点:<a>,<h1>,<body>,<html>等,即标签
(2)文本节点:向用户展示的内容(元素节点和属性节点的内容),如中的“文档标题”文本<br/> (3)属性节点:元素的属性,如a标签的链接地址属性href<br/> (4)通过getElementById和document.body等方法获取元素就是获取元素节点的

DOM结构优化

1、在优化之前,先了解DOM为什么这么慢

  • 因为收了“过路费”

把 DOM 和 JavaScript 各自想象成一个岛屿,它们之间用收费桥梁连接。——《高性能 JavaScript》

JS引擎和渲染引擎是独立实现的,当我们用JS去操作DOM时,本质上是JS引擎和渲染引擎两个模块之间的协作。我们每操作一次DOM(无论是修改还是仅仅访问其值),都要过一次“桥”。过”桥“的次数一多,就会产生明显的性能问题。因此,要“减少DOM操作”。

  • 对DOM的修改引发样式的更迭
    过桥很慢,到了桥对岸,我们更改操作带来的结果也很慢。
    我们对DOM的操作很多时候不仅是访问它,更多是修改它,当我们对DOM的修改会引起它样式上的改变时,就会触发回流和重绘
    2、DOM优化
  • 减少DOM访问次数
  • 多次访问同一DOM,应用局部变量缓存该DOM
  • 尽可能使用querySelector,而不是获取HTML集合的API
  • 注意重排重绘
  • 使用事件委托【用addEventListener(type,listener,useCapture)实现】,减少绑定事件的数量

一个小栗子:
首先有如下代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>DOM操作测试</title>
</head>
<body>
  <div id="container"></div>
</body>
</html>

此时有一个假需求——向container元素里写10000句一样的话,如果这样做:

for(var count=0;count<10000;count++){ 
  document.getElementById('container').innerHTML+='<span>我是一个小测试</span>'
} 

这段代码有两个明显的可优化点。
第一点:我们每一次循环都调用 DOM 接口重新获取了一次 container 元素,相当于每次循环都去操作DOM,这样不必要的DOM操作过多。我们可以通过缓存变量的方式来节省不必要的渲染:

// 只获取一次container
let container = document.getElementById('container')
for(let count=0;count<10000;count++){ 
  container.innerHTML += '<span>我是一个小测试</span>'
} 

第二点,不必要的 DOM 更改太多了。我们的 10000 次循环里,修改了 10000 次 DOM 树。我们前面说过,对 DOM 的修改会引发渲染树的改变、进而去走一个(可能的)回流或重绘的过程,而这个过程的开销是很“贵”的。这么贵的操作,我们竟然重复执行了 N 多次!其实我们可以通过就事论事的方式节省下来不必要的渲染:

let container = document.getElementById('container')
let content = ''
for(let count=0;count<10000;count++){ 
  // 先对内容进行操作
  content += '<span>我是一个小测试</span>'
} 
// 内容处理好了,最后再触发DOM的更改
container.innerHTML = content

就事论事,即JS层面的事情,JS自己去处理,处理好再来找DOM。
在上面的例子里,字符串变量content扮演 一个DOM Fragment 的角色,他们本质上都作为脱离真实DOM树的容器出现,用于缓存批量化的DOM操作。
前面我们直接用innerHTML去拼接目标内容,但DOM Fragment可以帮我们更加结构化的方式达成同样的目的,从而在维持性能的同时,保住代码的可拓展和可维护性:

let container = document.getElementById('container')
// 创建一个DOM Fragment对象作为容器
let content = document.createDocumentFragment()
for(let count=0;count<10000;count++){
  // span此时可以通过DOM API去创建
  let oSpan = document.createElement("span")
  oSpan.innerHTML = '我是一个小测试'
  // 像操作真实DOM一样操作DOM Fragment对象
  content.appendChild(oSpan)
}
// 内容处理好了,最后再触发真实DOM的更改
container.appendChild(content)

浏览器工作的过程:

  • 从网络或本地拿到html源代码
  • 浏览器将html源码中的每个标签都实例化对应的对象
  • 在内存中形成一个DOM树
  • 将内存DOM树解析为可视页面
  • JS直接修改DOM树,改变页面视图
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值