DOM——页面的渲染、style属性操作、预加载与懒加载、防抖与节流

2023-10-26

一、页面的渲染

(一)浏览器加载一份HTML文档的加载过程

  1、把标签、文本、注释、属性等html代码解析为节点树(DOM Tree)
  2、把所有样式(css代码和浏览器自带)解析为结构体
  3、把css样式结构体和节点树结合变成呈现树/渲染树(Render Tree)
  4、根据渲染树Render Tree绘制页面

 

(二)重绘与回流

 1)回流:

  • 当render tree中因为元素的数量、布局、隐藏等改变而需要重新构建的称为回流或者回档 
  • 每个页面至少需要一次回流即在页面第一次加载的时候

  2)重绘:

  • 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格、样式等而不会影响布局的则称为重绘

  3)关系:

  • 任何对render tree中元素的操作都会引起回流或重绘
  • 回流必将引起重绘 重绘不一定引起回流

  4)常见的回流和重绘操作:

  1. 添加或删除元素(回流+重绘)
  2. 隐藏元素:如display:none(回流+重绘)    visibility:hidden(只重绘,不回流)
  3. 移动元素:如改变top,left(jquery的animate方法就是,改变top,left不一定会影响回流),或者移动元素到另外1个父元素中(重绘+回流)
  4. 对style的操作(对不同的属性操作,影响不一样)
  5. 用户的操作:如改变浏览器或浏览器字体的大小等(回流+重绘)

     示例:

 <style>
        .box {
            width: 200px;
            height: 200px;
            background-color: red;
            /* visibility: hidden; */
            /* display: none; */
        }
    </style>
    <div class="box">hello</div>
    <button onclick="fn()">change</button>
    <script>
        function fn() {
            var box = document.querySelector(".box")
            box.innerHTML = "6666" //改变了文档树的结果回流
            box.style.visibility = "hidden"; //元素只是隐藏 位置保留 =>重绘不回流
            box.style.display = "none"; //元素会消失 文档树中不会保留它的位置 =>回流
        }
    </script>

  5)影响:

  • 频繁的重绘/回流会使 计算机消耗过大 导致页面的性能和用户体验不好 

  6)解决办法:

  • 尽量避免重绘
  • 创建一个fragment   

fragment:

将内容添加到fragment里面 它自身不会添加到文档树中用于渲染  将里面的内容添加到文档树后它就会消失 即要添加的子元素--->添加进fragment--->将fragment添加到目标父元素中--->子元素进父元素,fragment消失(仅重绘、回流一次) 

在dom中叫=> fragment  在微信小程序中=> block   在vue中=> template    在react中=> </>

   案例:添加1万个格子到页面上 每个格子显示时间(ms)

<style>
#box td {
      border: 1px gainsboro solid;
 }
</style>
<table id="box">
</table>
<script>
 let tb=document.querySelector(".box")
        for(let i=0;i<100;i++){
        	let tr=document.createElement("tr")
        	tb.appendChild(tr)         //多次添加元素到文档树中 导致频繁的回流和重绘
        	for(let j=0;j<100;j++){
        		let dt=new Date().getTime()
        		let td=document.createElement("td")
        		td.innerHTML=dt
        		tr.appendChild(td)      //多次添加元素到文档树中 导致频繁的回流和重绘
        	}
        }
      //多次回流重绘
</script>

   优化:

 <style>
        #box td {
            border: 1px gainsboro solid;
        }
</style>
<table id="box"></table>
<script>
     //创建一个fragment元素来承载即将渲染的元素
        let tb=document.querySelector("#box")
        let fra1=document.createDocumentFragment() //它在内存中还不在文档中
        for(let i=0;i<100;i++){
        	let tr=document.createElement("tr")
        	fra1.appendChild(tr)
        	for(let j=0;j<100;j++){
        		let dt=new Date().getTime()
        		let td=document.createElement("td")
        		td.innerHTML=dt
        		tr.appendChild(td)
        	}
        }
      tb.appendChild(fra1) //它自己不会添加到文档树中用于渲染 但是它的子代元素都会添加进去
    //只向文档树中添加了一次 所以只回流1次
</script>

二、style的操作

(一)获取元素的问题

   引例:

<style>
    .box {
            width: 400px;
            height: 300px;
            background-color: aqua;
            cursor: pointer;
        }
</style>
<div class="box" style="color: red;">这是div1</div>
<script>
var body1 = document.body
var box2 = document.querySelector(".box2")
var color1 = document.querySelector(".box").style.color
var width1 = document.querySelector(".box").style.width
console.log(body1)   //body
console.log(box2)    //null 节点还没有加载完 访问不了后面的
console.log(color1)  //red
console.log(width1)  //    空的什么都没有
</script>
<div class="box2">这是div2</div>

 打印结果:

 

思考:为什么div2是null?

 原因:

  • 获取不了script脚本节点后面加载的元素节点,因为文档的加载是按照文档树的顺序加载的

 解决方案: 

  • 方案一:当页面加载完成的事件触发 再去执行获取节点的方式
    function fn() {
       var box2 = document.querySelector(".box2")
       console.log(box2)
    }
    window.onload = fn  
//window.onload会在页面加载完后执行 即fn也在页面加载完后执行 就可以在原位置访问到div2
  • 方案二:通过引入script标签的defer、async(修饰src的加载外部js资源的)异步属性

                      将代码写到外部js文件中

//index.js 文件中:
<script>
var box2 = document.querySelector(".box2")
console.log(box2)
</script>
//html文件中:
<script defer src="index.js"></script>
<div class="box2">这是div2</div>

async 和 defer
脚本的异步加载
1.<script src="script.js"></script>
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。

2.<script async src="script.js"></script>
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。

3.<script defer src="myscript.js"></script>
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。

从实用角度来说:首先把所有脚本都丢到 </body> 之前是最佳实践,因为对于旧浏览器来说这是唯一的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。
 

 

(二)style属性问题

   1.行内样式 :el.style.xx    ele.currentStyle (IE的低版本)

  • 只能操作行内样式 没有兼容问题  
  • 只能设置为字符串  样式必须用驼峰命名法 
  • 遇到与保留字一样的样式,前面应加“css”(eg:float—>el.style.cssFloat)
  • 其实是获取的文档树中的元素的属性值

  2.最终绘制样式:window.getComputedStyle(el)

  •  其实是获取的呈现树中的元素的属性值

  3.拓展:window.getComputedStyle(ele,"after")

  • 第二个参数解决的是获取伪元素样式

三、防抖与节流

1) 防抖:

  •   触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
  •  思路:每次触发事件时都取消之前的延时调用方法

  案例:

document.onclick = fangdou(function(e) {
    	console.log(6666)
    }, 1000)
    function fangdou(cb, delay) {
    	var timer = null;
    	return function() {
    		//return这个函数频率很高  想办法让cb()执行慢下来:防抖
    		clearTimeout(timer)
    		timer = setTimeout(function() {
    			cb()
    		}, delay)
    	}
    }

 优化:

document.onclick = fangdou2(function(e) {
        console.log(6666,e,this)
    }, 1000)
    
    function fangdou2(cb, delay) {
        var timer = null;
        return function() {
            //return这个函数频率很高  想办法让cb()执行慢下来:防抖
            let arg=arguments
            clearTimeout(timer)
            timer = setTimeout(()=>{
                cb.apply(this,arg)
            }, delay)
        }
    }


2)节流:

  • 高频事件触发,但在n秒内只执行一次,所以节流会稀释函数的执行频率
  • 思路:每次触发事件时都判断当前是否有等待执行的延时函数

案例:

document.onmousemove=jieliu(function(){console.log(666)},20)
    function jieliu(cb,daley){
    	var timer=null;
    	return function(){
    		if(!timer){
    		  timer=setTimeout(()=>{
    			  cb();						  
    			  timer=null
    		  },daley)						
    		}
    	}
    }

优化:

function jieliu2(cb,daley){
        var timer=null;
        return function(){
            // console.log(66666,timer)
            // this是事件函数的this
            let arg=arguments
            if(!timer){
              timer=setTimeout(()=>{
                  cb.apply(this,arg); //本来是window但是希望是cb					  
                  timer=null
              },daley)						
            }
        }
    }

四、预加载与懒加载

1)预加载: 提前加载资源--同源加载的优化

  案例:

  • 相同的图片加载一次后就保存在浏览器的本地中,不用多次加载
<!-- 同时加载三张相同的图片-->
<img src="https://img2.baidu.com/it/u=1814268193,3619863984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=d974afe62dd1225c4faa14b1538bf7f5">		
<img src="https://img2.baidu.com/it/u=1814268193,3619863984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=d974afe62dd1225c4faa14b1538bf7f5">		
<img src="https://img2.baidu.com/it/u=1814268193,3619863984&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=d974afe62dd1225c4faa14b1538bf7f5">

  效果: 

2)懒加载: 先不加载 等待某个条件成立时再加载资源

  案例

  • 当条件成立才加载页面之下的图片 保证在滑到图片位置之时就已经加载好了 能看到图片
<p>hello</p><p>hello</p><p>hello</p><p>hello</p><p>hello</p>
<p>hello</p><p>hello</p><p>hello</p><p>hello</p><p>hello</p>
<!--此处省略很多个p标签-->
<p>hello</p><p>hello</p><p>hello</p><p>hello</p><p>hello</p>
<p>hello</p><p>hello</p><p>hello</p><p>hello</p><p>hello</p>
<script>
window.onload=function(){
  document.onscroll=function(e){
	let top=window.pageYOffset||document.body.scrollTop||document.documentElement.scrollTop
	let h=cHeight =window.innerHeight||document.body.clientHeight;
	console.log(top,img2.offsetTop-h-100)
	if(top>=(img2.offsetTop-h-100)){
			img2.src=img2.dataset.src1  //条件成立加载图片
	}
  }
}			
</script>
<img  id="img2" data-src1="https://img1.baidu.com/it/u=1966616150,2146512490&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1658422800&t=f99225a791b634226dcd5ee47c8b5f3f">

  效果:

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

DOM——页面的渲染、style属性操作、预加载与懒加载、防抖与节流 的相关文章