关闭浏览器网页触发事件_浅析浏览器渲染和 script 加载

5a2303deaad679ac776d9a811bb0df1b.png

前言

前端代码离不开浏览器环境,理解 js、css 代码如何在浏览器中工作是非常重要的。

如何优化渲染过程中的回流,重绘?script 脚本在页面中是怎么个加载顺序?了解这些对前端性能优化起着非常大的作用。

借着这篇文章,让自己对这块知识的理解更深一步。

渲染

渲染树(Render Tree)

浏览器通过解析 HTML 和 CSS 后,形成对应的 DOM 树和 CSSOM 树。

从根节点开始解析 DOM 树节点并匹配对应的 CSSOM 样式规则,选择可见的的节点,最终结合成一颗渲染树

e6bc3e8f0a7ad266d6bd3f2b4280e86d.png

从上图能看到渲染树的特点:

  • 渲染树中不包含 head、script、link、meta 之类不可见的节点
  • CSS 定义的样式规则将和实际的 DOM 匹配,并且被 display:none 修饰的节点最终不会出现在渲染树中

渲染阶段

8dcc8d4bac16596dbf1ffffe8b76db42.png

根据上图,整个渲染阶段分为三部分:

  • 渲染树的形成:通过 DOM 和 CSSOM 形成渲染树
  • 布局 Layout(自动重排 Reflow):基于页面的流式布局,遍历渲染树节点,不断计算节点最终的位置,几何信息,样式等属性后,输出一个“盒模型”
  • 绘制 Paint(栅格化):将节点位置,大小根据屏幕的窗口大小换算成真实的像素,同颜色等属性一同“画到”页面上

回流和重绘

基本概念

  • 回流 Reflow:某些元素位置、几何形状的更改需要浏览器重新计算相关元素。
  • 重绘 Repaint:将回流重排好的元素绘制到页面上,但也因某些 js、css 的修改导致渲染树发生变化,浏览器需要再次绘制页面。

两者的关系:触发回流一定会触发重绘, 而触发重绘却不一定会触发回流

下图很形象的展示了 Mozilla 页面的渲染过程。

dcdfbb214ab35f3a554b5f94f476e408.gif

触发回流条件

  • 首次布局渲染页面
  • 改变浏览器窗口大小
  • 改变字体
  • 网页内容变化
  • 触发 CSS 伪类
  • 操作 DOM
  • style 样式表发生变化
  • 调用 DOM 元素的 offsetXX, clientXX,scrollXX,getClientRects 等属性方法,获取元素当前的位置度量信息(参见)

如何测试网页性能

都知道频繁的渲染过程会影响网页性能,但怎么知道网页开始渲染内容了呢?

我们可以通过 Chrome 的 F12,选择 Rendering 来查看网页的性能。

cadb0ab9fbb76bd77ee345da1d603242.png
58007af69f8c0795b058735812c57f59.png
  • Paint flashing: 以绿色高亮重绘区域
  • Layout Shift Regions: 以蓝色高亮布局发生变化的区域

结合上面的方法,用 一个简单的 Demo 来示意:

5d102ca3e7b9669dd7786d3b2708c95e.gif

能从图中看到,这些操作 触发了浏览器的重绘

  • 鼠标移至按钮上,触发了默认的 hover 效果(出现绿框)
  • 改变元素 color 属性(出现绿框)
  • 修改元素 top 属性,不断改变元素位置影响布局(出现绿框,蓝框)

提升渲染性能

布局/回流绘制/重绘 是页面渲染必须会经过的两个过程,不断触发它们肯定会增加性能的消耗。

浏览器会对这些操作做优化(把它们放到一个队列,批量进行操作),但如果我们调用上面提到的 offsetXX, clientXX,scrollXX,getClientRects 等属性方法就会强制刷新这个队列,导致这些队列批量优化无效。

下面列举一些简单优化方式:

  • 不要使用 table 布局 table 布局会破坏 HTML 流式解析过程,甚至内部元素改动会触发整个 table 重绘
  • 将需更改的 class 放到最里层 明确元素位置,减少父类元素不必要渲染判断
  • 使用 fixed、absolute 属性修饰复杂多变的处理(动画) 将改变范围降到最低程度,避免影响到父级元素
  • 合并,减少 DOM 操作;通过虚拟 DOM 来代替

脚本的加载

link 和 script 加载文件的差异

注:均放在 head 标签内。

考个问题:CSS 定义在 head 中,其需加载 5 秒,请问页面加载后内容会先优先展示吗?

          
我被渲染出来了

我原先以为页面内容会优先渲染,CSS 加载完成后才改变内容样式。其实这是错的。

ec655f5d353fcd6f94e72e9b5acc469a.gif

从上图看到,页面加载后,body 内元素就已经解析好了,只是没有渲染到页面上。随后 CSS 文件加载后,带有样色的内容才被渲染到页面上。

延迟的 link 的加载阻断了页面渲染,但并没有影响 HTML 的解析,当 CSS 加载后,DOM 完成解析,CSSOM 和 DOM 形成渲染树,最后将内容渲染到页面上。

反问,将 link 替换成 script 效果也一样吗?

a2bfb539740d7610d9a68d6732147bd3.gif

与 link 不同,script 的加载会阻断页面 HTML 的解析,浏览器解析完 script 后,会等待 js 文件加载完后,页面才开始后续的解析,body 内容才出现。

head 和 body 中的 script 标签

学前端时相信都听过这样的名言:

CSS 写在 head 里,js 写在 body 结束标签前

知道了上面 link 和 script 的区别后,应该明白前半句的含义,下面来解释下后半句。

下面 script 均在 body 中

页面渲染 和 script 加载

先看下脚本在 body 中的一般情况:

在 body 内部的首位分别加载两个 js 文件,前者延迟 3 秒,后者延迟 5 秒,为了清楚他们的“工作”情况,在 head 中添加了定时器示意。

                      
我被渲染了
6767c0f66ed6c6812e9acee88fd5db4c.gif

能看到 body 中定义的内联脚本首先工作,初始化 foo 变量。

随后加载 addTen.js,并阻断页面渲染。3 秒后,输出 js 内容(foo 赋值为 10),页面并重新开始解析,展示 div 内容。

最后加载 addOne.js ,继续等待 2 秒后,输出 js 内容(foo 赋值为 11)。

340572169c396f7a46fd995cad51d650.png

多个 script 文件的加载

如果前一个 js 文件加载慢于后一个,会有怎么个效果?

我被渲染了

两个 script 标签并行加载,1 秒后 addOne.js 首先加载完毕,等待 4s 秒后,addTen.js 加载完后,页面直接渲染(因为 script 已经全部完成)。

aabac8bbee4e39d478b8d8e230b46016.png

简单总结下

  1. 无论在 head 还是 body 中,浏览器会等待 script 文件的加载(阻断页面解析渲染)
  2. 多个 script 的文件加载是异步的,不存在互相影响(后一个文件不需要等待前一个加载完后才下载),执行顺序同定义顺序

所以建议 script 放在 body 结束标签之前,确保页面内容全部解析完成并开始渲染。

DOM 的 DOMContentLoaded 事件

DOMContentLoaded 事件可以来确定整个 DOM 是否全部加载完成,下面我们简单测试下:

我被渲染了

最终输出:

addTen.jsfoo 10addOne.jsfoo 11[ready] document

DOMContentLoaded 事件的定义是异步回调方式,当 DOM 加载完成后触发,即使写在最前面,也会等待后面的 script 加载完成后才触发。

这里顺便提个 window.onload

window.onloadDOMContentLoaded 不同,前者会等待页面中所有的资源加载完毕后再调用执行(比如:img 标签),后者在 DOM 加载完毕后即触发。

“真正的异步脚本”——动态脚本

能看到无论 script 放在那个位置,浏览器都会等待他们直至 body 内的文件全部加载完。

那有什么 真正的异步 脚本加载吗?(不会阻断页面解析)

那就是 动态脚本

如果你接触过第三方网页统计脚本,那将比较了解,下面给段示例代码:

我被渲染了

最终输出:

addTen.jsafoo 10addOne.jsfoo 11[ready] document已加载  5  秒已加载  6  秒已加载  7  秒已加载  8  秒dynamicScript.js is runningdynamicScript.js loaded已加载  9  秒已加载  10  秒
668e619bffa63684a22df20150441af2.png

定义了需要加载 8 秒的 dynamicScript.js 文件,所有的 script 加载方式依旧异步,但 dynamicScript.js 在 DOMContentLoaded 触发后,最后才执行,浏览器并没有等待它的加载完成后才渲染页面。

我们也可以将它放在 head 中。这种通过脚本来动态修改 DOM 结构的加载方式是 无阻塞式 的,不受其他脚本加载的影响。

defer 和 async

我们可以在 script 定义 deferasync ,使整个脚本加载方式更加友好。比如:被修饰的脚本在 head 中,将不会阻断 body 内容的展示

注意: defer 修饰的脚本将延迟到 body 中所有定义的脚本之后,DOM(页面内容)加载完之前触发async 不会像 defer 一样等待 body 中的脚本,而是当前脚本一加载完毕就触发。

                  
我被渲染了

加载顺序:

已加载  1  秒已加载  2  秒scriptAsync.js已加载  3  秒已加载  4  秒addTen.jsfoo 10addOne.jsfoo 11scriptDefer.js[ready] document已加载  5  秒已加载  6  秒已加载  7  秒已加载  8  秒dynamicScript.js is runningdynamicScript.js loaded已加载  9  秒已加载  10  秒

本文使用 mdnice 排版

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/455394.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Open vSwitch实验常用命令

1. 基本架构 ovs-vsctl: 管理ovsdb-server的配置,提供OVSDB的配置方法,包括创建和删除网桥、端口等; ovs-ofctl: 提供ovs-vswitchd的流表配置方法; ovs-dpctl: 配置OVS内核模块,提供缓存流表的操作方法&#xff1b…

记IOS8中碰到的一个JS bug

IOS8的JS版本过低导致 var id "123"; var temp1 {id, "left": "200"}; // error in IOS8 var temp2 {"id":id, "left": "200"};平时还是多写ES5的代码,es6的语法总能碰到兼容的坑。 改了好几天。…

Emmet的html语法

Emmet的html语法 所有操作按下“tab”键即可瞬间完成 元素 1.在编辑器中输入元素名称,即可自动补全生成 HTML 标签,即使不是标准的 HTML 标签。 2.输入:! 或者 html:5 或者 html:4s 或者 html:4t 将自动补全html基本结构 嵌套操作 1.使用…

RTP Payload Format for H.264 Video

H.264 RTP协议的封装格式rfc3984 英文原版:http://tools.ietf.org/html/rfc3984 部分中文翻译: H.264 视频 RTP 负载格式 1. 网络抽象层单元类型 (NALU) NAL单元1字节包头负载 NALU 头由一个字节组成, 它的语法如下: —————|0|1|2|3|4|5|6|7|------…

js字符串、数组和数字常用方法总结

https://github.com/AnHyun/blog/issues/3 一、string 常用方法: 1.substring(start开始位置的索引,end结束位置索引) 截取的位置不包含结束位置的字符,只写一个参数表示从开始位置截取到最后,输入负值时将负值变为0,哪个较小作为开始位置 va…

Oracle 存储过程错误之PLS-00201: 必须声明标识符

转自:http://blog.csdn.net/u010678947/article/details/20702149 错误: ORA-06550: 第 1 行, 第 7 列: PLS-00201: 必须声明标识符ZUO.PROCE_TESTORA-06550: 第 1 行, 第 7 列: PL/SQL: Statement ignored 解决方法: (1&#x…

mysql中如何把两个查询结果列数不同并成一张表_MySQL

引言本文整理了MySQL相关的知识,方便以后查阅。 基础架构下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。 先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图。 - 连接器: …

JavaWeb笔记01-XML

今日内容 XML 概念语法解析 XML: 概念: Extensible Markup Language 可扩展标记语言 可扩展:标签都是自定义的.<user><student> 功能 存储数据 配置文件在网络中传输 一个故事 由于浏览器之间的竞争,导致HTML发展的十分不顺利 用户:唉,这怎么报错了呢?…

centos下如何使用sendmail发送邮件

最近在实施服务端日志监控脚本&#xff0c;需要对异常情况发送邮件通知相关责任人&#xff0c;记录下centos通过sendmail发送邮件的配置过程。一. 安装sendmail和mailx1、安装sendmail&#xff1a;1): centos下可以安装命令:yum install -y sendmail service sendmail start yu…

H.263 H.263+ Payload Type

h263 rtp协议封装协议英文版&#xff1a;rfc4629:http://tools.ietf.org/html/rfc4629 以下文章是部分参考翻译&#xff1a; 文章出处&#xff1a; http://blog.csdn.net/zblue78/archive/2009/04/09/4059414.aspxGeneral H.263 Payload Header The H.263 payload header is s…

OC 中 load 方法和 initialize 方法的异同

(void)load; 当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类load 方法不会被类自动继承 (void)initialize; 也是在第一次使用这个类的时候会调用这个方法 转载于:h…

scrapy框架_Python学习之Scrapy框架

爬虫界江湖地位No.1说起Python&#xff0c;不得不说到它的爬虫应用&#xff0c;由于Python的短小精悍&#xff0c;用它来开发爬虫应用是最合适不过了&#xff0c;基于Python抓取网页的库有很多&#xff0c;例如requests,beatifulsoup等等&#xff0c;但是要说到有哪一个框架&am…

JavaWeb笔记03-Servlet

今日内容 ServletHTTP协议Request Servlet 概念 步骤 执行原理 生命周期 Servlet3.0注解配置 Servlet的体系结构 Servlet – 接口 GenericServlet – 抽象类:将Servlet接口中其他方法做了默认空实现,只将service()方法作为抽象 将来定义Servlet类时候,可以继承Generic…

Android开发中无处不在的设计模式——动态代理模式

继续更新设计模式系列。写这个模式的主要原因是近期看到了动态代理的代码。 先来回想一下前5个模式&#xff1a; - Android开发中无处不在的设计模式——单例模式 - Android开发中无处不在的设计模式——Builder模式 - Android开发中无处不在的设计模式——观察者模式 - A…

用于MPEG-4视听流的RTP负载格式

MPEG-4的rtp协议封装英文原版 RFC 3016&#xff1a;http://www.rfc-editor.org/rfc/rfc3016.txt中文翻译&#xff1a;组织&#xff1a;中国互动出版网&#xff08;http://www.china-pub.com/&#xff09;RFC文档中文翻译计划&#xff08;http://www.china-pub.com/compters/emo…

pycharm python 模板配置_windows下pycharm安装、创建文件、配置默认模板

本文为大家分享了windows下pycharm安装、创建文件、配置默认模板的具体步骤&#xff0c;供大家参考&#xff0c;具体内容如下步骤&#xff1a;下包 —->安装——>创建文件—->定制模板一、下包官方地址这里有企业版和社区版&#xff0c;老司机都知道社区版是免费的&am…

JavaWeb笔记02-Tomcat

今日内容 web相关概念回顾web服务器软件:TomcatServlet入门学习 web相关概念回顾 软件架构 C/S: 客户端/服务器端B/S: 浏览器/服务器端 资源分类 静态资源: 所有用户访问后,得到的结果都是一样的,成为静态资源,静态资源可以直接被浏览器解析 如:html, css ,JavaScript 动态资…

网上的画板代码收集和整理

修改后的代码[1]为&#xff0c;少了一个} package com.example.administrator.myapplication;import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.graphics.Canvas; import an…

如何写年终总结(转)

很多人不重视年终总结&#xff0c;觉得是一个非常令人厌烦的任务&#xff0c;往往是应付了事&#xff0c;短短几百字&#xff0c;对目前工作中存在的问题发现不够&#xff0c;思考不足&#xff0c;对自己一年的评价和未来一年的定位没有说明。造成的后果就是公司得不到来自基层…

cad移动时捕捉不到基点_CAD入门必备(一)移动和复制新手必看

cad也疯狂前言&#xff1a;CAD绘图之所以能够取代手工绘图&#xff0c;很大的一部分原因是因为它可以很方便的修改和重复利用&#xff0c;例如外参可以节省很大部分时间。而我们在使用CAD中&#xff0c;用得最频繁的功能就是移动和复制了&#xff0c;当然这也是新手必备的其中一…