易语言浏览器是否就绪_彻底搞懂浏览器渲染页面的机制和原理

本文深入探讨浏览器渲染页面的过程,包括HTTP请求、响应阶段以及渲染阶段的详细步骤。重点讲解渲染阶段的DOM树构建、CSSOM树生成、Render Tree的形成、回流与重绘的原理,以及性能优化策略,如减少HTTP请求次数、避免DOM的重绘和回流等。
摘要由CSDN通过智能技术生成

a7c6c3c22c4e5b47f04d073d700d7b18.png

1、一道面试题:从用户在浏览器地址栏输入网址,到看到整个页面,中间发生了哪些事情?

答:共有三个阶段: ①http请求阶段 ②http响应阶段 ③浏览器渲染阶段

在这里我们先重点讲一下浏览器渲染阶段

扫盲:

① 进程:Process(开了多个饭店)

② 线程:Thread(一个饭店雇佣一个服务员是单线程,雇佣多个服务员是多线程)

③ 栈内存:Stack (提供一个环境,运行代码)

2、浏览器渲染页面的机制和原理

第一阶段:

当输入url后,比如(http://www.baidu.com),客户端(浏览器)向服务器端(web服务器)发起请求,开始进入Request请求阶段,这个阶段包含:DNS解析、TCP协议的三次握手和四次挥手、HTTPS和HTTP的区别(HTTP2)

第二阶段:

在服务器端的项目磁盘中保存着相对应的页面,这些页面是源代码的格式,请求到以后就开始进入Response阶段HTTP状态码、304缓存、HTTP报文

第三阶段:

浏览器拿到源代码以后,浏览器在内存条开辟出一块栈内存,用来给代码的执行提供环境。

只有执行环境是不够的,这时候浏览器同时分配一个主线程一行一行的解析和执行代码。(前端js是单线程的,原理是浏览器只会分配一个线程来去解析代码)(内存条的空间越高越好,因为这样分出来的空间就越多,做的事情也越多)

而这一个线程去解析源代码的过程:它得先进栈才能执行,所以首先进栈,每一个页面第一行都是:<!DOCTYPE html>声明文档类型是html,因为单线程一次只能干一件事,所以当前代码执行完以后,主线程一定要出栈,只有出栈,主线程才有时间执行下一行代码。然后再进栈出栈如此这般一行一行的解析。

解析过程中,会遇到<link href=”1.css”/>,这时候主线程有两种选择。

第一种:自己亲自去服务器,但这个时候浏览器就没人了,下面的代码无法执行,所以必须得将1.css搬过来才能执行,这种方法是不好的。

第二种:让自己的朋友帮忙去服务器搬1.css,自己(主线程)继续执行下面的代码,这个时候浏览器中就有两个人干活了。所以当代码中遇到<link/>、<img/>、<script/>、<audio/>、<video/>等这些所有需要额外加载外部资源文件的请求后,都会单独开辟新的线程去加载资源文件,主线程继续向下执行。

因此可以看出浏览器是多线程程序,但是它只分配了一个线程用来执行页面当中的代码,所以js是单线程的。

打个比方,你去餐厅吃饭,点餐后,服务员(线程)会通知厨师去做饭,而服务员再去招呼其他客人点餐,厨师属于新雇的(属于另外一个线程),但是,这个餐必须得知道是给谁做的,这时候就得拿出一个本记上:当前谁点了什么菜,记一个点菜单。

同样的,浏览器也会做类似的事情,它会开辟一个新的内存空间--等待任务队列(Task Queue)。它是一个新的空间,比如任务一:(需要请求1.css)交给张三去做,任务二:(需要请求2.css)交给李四去做......,同时,主线程继续执行下面的代码,当执行完最后一行代码后,这个时候需要搬运的css文件全都搬回来了吗?答案是不一定。这个时候只是把html标签,也就是DOM结构渲染完了。

所以,浏览器加载页面第一个环节是:DOM树渲染

只有DOM树,页面是不会渲染出来的,接着就要把做好的饭端上来,也就是,当DOM树渲染完以后,它会到等待任务队列中去看哪个任务完成了,哪个任务完成,就把哪个任务拿回到栈内存中去执行。

比如任务二完成先拿回来了,主线程就先执行任务二,当在执行任务二的时候,任务一也完成了,js是不会去马上执行任务一的,必须要等到任务二执行完成以后才可以,因为js是单线程的,一次只能执行一件事情。当任务一执行完以后,再去任务队列中看其他任务是否完成,如果任务三完成了再拿回到执行栈中执行任务三,如此循环往复,这就是前端著名的事件循环(Event Loop)。

等待任务队列中还分为宏任务(setTimeout、setInterval)和微任务(promise),微任务是优先于宏任务执行的。

当css处理完成以后,会生成CSSOM

这个时候,浏览器会将DOM和CSSDOM结合起来生成Render Tree(渲染树)

一切准备就绪后,开始绘制:通知电脑的gpu(显卡),按照渲染树,一点点的画。

其实在生成render tree(渲染数)后,还有两个阶段,第一个是,回流(Layout):根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流。

回流以后还不行,我该画几像素,画成什么颜色呢?这就需要重绘

第二个就是:重绘(Painting):根据渲染树已经回流得到几何信息,得到节点的绝对像素。

总结:DOM树渲染——> CSSDOM树渲染 ——>生成Render Tree(渲染树)——>Layout(回流)——>Painting(重绘)——>Display(GPU展现在页面)

知道这些有什么用呢?难道就是为了装一下X吗?

当然不是,它对我们大家的意义特别大。比如,在开发中我们会遇到一个经典的概念,面试官也会经常问:你之前的项目中做过性能优化吗?

比如刚才的请求css的文件,是请求次数多好,还是少好呢?

当然是请求次数少好,所以前端性能优化第一条便是:减少http请求次数和大小(①资源合并压缩 ②图片懒加载 ③ 音视频走流文件等)。

86c347671f7a88eb5b314c2b35c97a72.png

3、深入了解重绘和回流

重绘:元素样式的改变(但宽高、大小、位置不变)

如:color、background-color

回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染。

如,添加或删除可见的DOM元素;元素的位置发生变化;元素的尺寸发生变化;内容发生变化;页面一开始渲染的时候(这个无法避免);因为回流是根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流

注意:回流一定会触发重绘,而重绘不一定触发回流

回流和重绘都会影响性能,尤其是回流,所以第二条优化法则:尽可能减少避免DOM的重绘和回流

如何避免DOM的回流呢?(不操作DOM不就行了)

放弃传统操作DOM的时代,基于vue/react开始数据影响视图模式

mvvm/mvc/virtual dom/dom diff...

分离读写操作(现代的浏览器都有渲染队列的机制)

什么叫分离读写呢?

举个例子

<style>
  #box{
    width:100px;
    height:100px;
    border:10px solid #ddd;
  }
</style>

<body>
  <div id="box"></div>
  <script>
    let box = document.getElementById('box')
    box.style.width='200px';
    box.style.height='200px';
    box.style.margin='10px';
  </script>
</body>

一开始div会宽高为100px,这个时候我用js将div的宽高改变为200px,margin为10px,以前的老浏览器的话就会执行三次回流,因为改变了三次样式的大小;而现在新的浏览器增加了渲染队列,也就是当执行box.style.width的时候,浏览器会将它加入渲染队列中,然后顿一下,查看下一行是否还是修改样式,如果是则再加入到渲染队列,一直到下一行代码不是修改样式为止,然后统一刷新渲染队列进行一次回流,所以上面虽然修改了三次,但是只执行了一次回流。

但是如果在中间加入了一行不是修改样式的代码,比如console.log,则浏览器就会直接渲染一次(也就是执行回流),然后继续向下执行,这时候就要执行两次回流,修改样式的代码就是写,console.log获取数据就是读,所以读写分开写就会提升性能,这就是读写分离

box.style.width='200px';
console.log(box.clientWidth);
box.style.height='200px';
box.style.margin='10px';

但是有一个问题,当下一行代码有这些时,及时下一行是修改样式,也会直接渲染执行回流。

offsetTop、offsetLeft、offsetWidth、offsetHeight、clientTop、clientWidth、clientHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、getComputedStyle、currentStyle...会刷新渲染队列

样式集中改变(批量处理)

需要修改的样式统一定一个类,然后加入到DOM中

.a{
 height:200px;
 width:200px;
}
<script>
 box.className=’a’
</script>

缓存布局信息

<script>
  box.style.width = box.clientWidth +10 +’px’;
box.style.height= box.clientHeight +10 +’px’
</script>

因为每一行有clientWidth、clientHeight 所以还是会执行两次回流,想执行一次可以这样

let a=box.clientWidth 
let b=box.clientHeight
box.style.width = a+10 +’px’;
box.style.height=  b+10 +’px’

这就是缓存处理

元素批量修改

动态在ul中加入5个li

<ul id=”box”></ul>

script部分

for(let i=0; i<5;i++){
 let newLi = document.createElement(‘li’)
 newLi.innerHTML = i
 box.appendChild(newLI)
}

上面的for循环,每循环一次就会想ul中加一个li子节点然后就会发生一次回流,一共执行了五次回流。

那如何才能批量处理呢?

方案一:(这种方式不常用)

我们可以用文档碎片(临时容器):createDocumentFragment

let frg = document.createDocumentFragment();
for(let i=0; i<5;i++){
 let newLi = document.createElement(‘li’)
 newLi.innerHTML = i
 frg.appendChild(newLI)
//创建的li放到文档碎片中,页面中还没有,所以不会执行回流
}
box.appendChild(frg) //一次性把内容放到box中,执行一次回流
frg=null //销毁,释放内存

方案二:字符串拼接

let str =``;
for(let i=0;i<5;i++){
  str+=`<li>${i}</li>`
}
box.innerHTML = str;

动画效果应用到postion属性为absolute或fixed的元素上(脱离文档流)

开启css3硬件加速(GPU加速)

能用transform做的就不要用其他的,因为transform可以开启硬件加速,而硬件加速可以规避回流。

#box{
 postion:absolute;
 width:100px;
 height:100px;
 background:red;
}

script部分
box.style.left='100px' //向右移动100px,一次回流
box.style.ctransform='translateX(200)' //向右移动200px,不会引发回流

牺牲平滑度换取速度

比如做一个动画,如果一像素一像素的移动,如果使用了100%CPU,动画会看上去是跳动的,因为浏览器正在与更新回流作斗争,每次移动3像素,虽然平滑度低了,但是不会导致cpu在较慢的机器中抖动

避免table布局和使用css的JavaScript表达式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值