js css 加载顺序
说渲染优化之前,我们还需要说一个小插曲,就是比较经典的一道问题浏览器地址栏输入url发生了什么理解了这个我们才可以更清楚js,css加载顺序对渲染的影响
问题 1:地址栏输入url 发生了什么
这个问题经常被人提起,有人回答比较简洁点,有人可能回答的比较详细,下面就说一下主要流程:
- 首先会进行 url 解析,根据 dns 系统进行 ip 查找
- 根据 ip 就可以找到服务器,然后浏览器和服务器会进行 TCP 三次握手建立连接,如果此时是 https 的话,还会建立 TLS 连接以及协商加密算法,这里就会出现另一个需要注意的问题”https 和 http 的区别”(下文会讲到)
- 连接建立之后浏览器开始发送请求获取文件,此时这里还会出现一种情况就是缓存,建立连接后是走缓存还是直接重新获取,需要看后台设置,所以这里会有一个关注的问题”浏览器缓存机制”,缓存我们等会在讲,现在我们就当没有缓存,直接去获取文件
- 首先获取 html 文件,构建 DOM 树,这个过程是边下载边解析,并不是等 html 文件全部下载完了,再去解析 html,这样比较浪费时间,而是下载一点解析一点
- 好了解析到 html 头部时候,又会出现一种问题,css,js 放到哪里了?不同的位置会造成渲染的不同,此时就会出现另一个需要关注的问题”css,js 位置应该放哪里?为什么”,我们先按照正确的位置来说明(css 放头部,js 放尾部)
- 解析到了 html 头部发现有 css 文件,此时下载 css 文件,css 文件也是一边下载一边解析的,构建的是 CSSOM 树,当 DOM 树和 CSSOM 树全部构建完之后,浏览器会把 DOM 树和 CSSOM 树构建成渲染树。
- 样式计算, 上面最后一句”DOM 树和 CSSOM 树会一起构建成渲染树”说的有点笼统,其实还有更细一点的操作,但是一般回答到上面应该就可以了,我们现在接上面说一下构造渲染树的时候还做了哪些事情。第一个就是样式计算,DOM树 和 CSSOM树有了之后,浏览器开始样式计算,主要是为 DOM 树上的节点找到对应的样式
- 构建布局树,样式计算完之后就开始构建布局树。主要是为 DOM 树上的节点找到页面上对应位置以及一些”display:none”元素的隐藏。
- 构建分层树,布局树完成后浏览器还需要建立分层树,主要是为了满足滚动条,z-index,position 这些复杂的分层操作
- 将分层树图块化,利用光栅找到视图窗口下的对应的位图。主要是因为一个页面可能有几屏那么长,一下渲染出来比较浪费,所以浏览器会找到视图窗口对应的图块,将这部分的图块进行渲染
- 最终渲染进程将整个页面渲染出来,在渲染的过程中会还出现重排和重绘,这也是比较爱问的问题”重排重绘为什么会影响渲染,如何避免?”
- 以上过程大概讲解了一下从 url 到页面渲染的整个过程,其实涉及到了几个需要关注的问题,下面来具体讲讲
问题 2:js css 顺序对前端优化影响
上面我们说到了整个渲染流程,但是没有说到 css 和 js 对渲染的影响。渲染树的构成必须要 DOM 树和 CSSOM 树的,所以尽快的构建 CSSOM 树是一个重要的优化手段,如果 css 文件放在尾部,那么整个过程就是一个串行的过程先解析了 dom,再去解析 css。所以 css 我们一般都是放在头部,这样 DOM 树和 CSSOM 树的构建是同步进行的。
再来看 js,因为 js 的运行会阻止 DOM 树的渲染的,所以一旦我们的 js 放在了头部,而且也没有异步加载这些操作的话,js 一旦一直在运行,DOM 树就一直构建不出来,那么页面就会一直出现白屏界面,所以一般我们会把 js 文件放在尾部。当然放到尾部也不是就没有问题了,只是问题相对较小,放到尾部的 js 文件如果过大,运行时间长,代码加载时,就会有大量耗时的操作造成页面不可点击,这就是另一个问题,但这肯定比白屏要好,白屏是什么页面都没有,这种是页面有了只是操作不流畅。
js 脚本放在尾部还有一个原因,有时候 js 代码会有操作 dom 节点的情况,如果放在头部执行,DOM树还没有构建,拿不到 DOM 节点但是你又去使用就会出现报错情况,错误没处理好的话页面会直接崩掉
问题 3:重排重绘为什么会影响渲染,如何避免?
重排和重绘为什么会影响渲染,哪个影响更大,如何避免是经常被问到的一道题目,我们先来说一下重绘
- 重绘
重绘指的是不影响界面布局的操作,比如更改颜色,那么根据上面的渲染讲解我们知道,重绘之后我们只需要在重复进行一下样式计算,就可以直接渲染了,对浏览器渲染的影响相对较小 - 重排
重排指的是影响界面布局的操作,比如改变宽高,隐藏节点等。对于重排就不是一个重新计算样式那么简单了,因为改变了布局,根据上面的渲染流程来看涉及到的阶段有样式计算,布局树重新生成,分层树重新生成,所以重排对浏览器的渲染影响是比较高的 - 避免方法
1.js 尽量减少对样式的操作,能用 css 完成的就用 css
2.对 dom 操作尽量少,能用 createDocumentFragment 的地方尽量用
3.如果必须要用 js 操作样式,能合并尽量合并不要分多次操作
4.resize 事件 最好加上防抖,能尽量少触发就少触发
5.加载图片的时候,提前写好宽高
问题 4:浏览器缓存机制
浏览器缓存是比较常见的问题,我会从浏览器缓存的方式,缓存的实现, 缓存在哪里这几个点来说明
缓存方式
我们经常说的浏览器缓存有两种,一种是强制缓存,一种是协商缓存,因为下面有具体实现讲解,所以这里就说一下概念
- 协商缓存
协商缓存意思是文件已经被缓存了,但是否从缓存中读取是需要和服务器进行协商,具体如何协商要看请求头/响应头的字段设置,下面会说到。需要注意的是协商缓存还是发了请求的 - 强制缓存
强制缓存就是文件直接从缓存中获取,不需要发送请求缓存实现
- 强制缓存
强制缓存在 http1.0 的时候用的是 Expires,是响应头里面的一个字段表示的是文件过期时间。是一个绝对时间,正因为是绝对时间所以在某些情况下,服务器的时区和浏览器时区不一致的时候就会导致缓存失效。为了解决这个问题,HTPP1.1 引入了一个新的响应头 cache-control 它的可选值如下
cache-control
max-age: 缓存过期时间,是一个相对时间
public: 表示客户端和代理服务器都会缓存
private: 表示只在客户端缓存
no-cache: 协商缓存标识符,表示文件会被缓存但是需要和服务器协商
no-store: 表示文件不会被缓存
HTTP1.1 利用的就是 max-age:600 来强制缓存,因为是相对时间,所以不会出现 Expires 问题 - 协商缓存
协商缓存是利用 Last-Modified/if-Modified-Since,Etag/if-None-Match 这两对响应头,请求头
Last-Modified/if-Modified-Since
浏览器第一次发送请求获取文件缓存下来,服务器响应头返回一个 Last-Modified,记录被改动的时间
浏览器第二次发送请求的时候会带上一个 if-Modified-Since 请求头,时间就是 Last-Modified 返回的值。然后服务器拿到这个字段和自己内部设置的时间进行对比,时间相同表示没有修改,就直接返回 304 从缓存里面获取文件
Etag/If-None-Match
由于 Last-Modified 的时间粒度是秒,有的文件在 1s 内可能被改动多次。这种方式在这种特殊情况下还是会失效,所以HTTP1.1又引入了 Etag 字段。这个字段是根据文件内容生成一个标记符比如”W/“5f9583bd-10a8””,然后再和 If-None-Match 进行对比就能更准确的知道文件有没有被改动过缓存在哪里
知道了缓存方式和实现,再来说一下缓存存在哪个地方,我们打开掘金可以看到如下的信息 。缓存的来源有两个地方from disk cache
,from memeory cache
form memory cache
这个是缓存在内存里面,优点是快速,但是具有时效性,当关闭 tab 时候缓存就会失效
from disk cache
这个是缓存在磁盘里面,虽然慢但是还是比请求快,优点是缓存可以一直被保留,即使关闭 tab 页,也会一直存在
何时缓存在memory,合适缓存在disk?
这个问题网上很少找的到标准答案,大家一致的说法是js,图片文件浏览器会自动保存在memory中,css文件因为不常修改保存在disk里面,我们可以打开掘金网站,很大一部分文件都是按照这个规则来的,但是也有少数js文件也是缓存在disk里面。所以他的存放机制到底是什么样了?我带着这个疑问查了好多文章,虽然最后没有确切找到答案,但是一个知乎的回答可以给我们提供思路,下面引用一个知乎回答者一段话 - 第一个现象(以图片为例):访问-> 200 -> 退出浏览器再进来-> 200(from disk cache) -> 刷新 -> 200(from memory cache)。总结: 会不会是chrome很聪明的判断既然已经从disk拿来了, 第二次就内存拿吧 快。
- 第二个现象(以图片为例):只要图片是base64 我看都是from memroy cache。 总结: 解析渲染图片这么费劲的事情,还是做一次然后放到内存吧。 用的时候直接拿
- 第三个现象(以js css为例):个人在做静态测试的发现,大型的js css文件都是直接disk cache。结: chrome会不会说 我去 你这么大太占地方了。 你就去硬盘里呆着吧。 慢就慢点吧
- 第四个现象:隐私模式下,几乎都是 from memroy cache。总结: 隐私模式 是吧。 我不能暴露你东西,还是放到内存吧。 你关,我死。
上面几点是虽然很幽默,但是却可以从中找到一部分答案,但是我觉得另一个知乎回答我更赞同浏览器运行的时候也是由几个进程协作的,所以操作系统为了节省内存,会把一部分内存里的资源交换回磁盘的交换区,当然交换是有策略的,比如最常用的就是LRU。
什么时候存disk,什么时候存memoey都是在浏览器控制下的,memory不够了可能就会考虑去存disk了,所以经过上面所说我自己总结结果如下
- 大一点的文件会缓存在disk里面,因为内存也是有限的,磁盘的空间更大
- 小一点文件js,图片存的是memory
- css文件一般存在disk
- 特殊情况memory大小是有限制的,浏览器也会根据自己的内置算法,把一部分js文件存到disk里面
请求优化
讲请求优化的之前先来总结下上面说到的js, css文件顺序优化,为了让渲染更快,我们需要把js放到尾部,css放到头部,然后还要注意在书写js的时候尽量减少重排,重绘。书写html,css的时候尽量简洁,不要冗余,目的是为了更快的构建DOM树和CSSOM树。好了下面我们在来说说请求优化,请求优化可以从请求数量和请求时间两方面入手
减少请求数量
- 将小图片打包成base64
- 利用雪碧图融合多个小图片
- 利用缓存上面已经说到过
减少请求时间
- 将js,css,html等文件能压缩的尽量压缩,减少文件大小,加快下载速度
- 利用webpack打包根据路由进行懒加载,不要初始就加载全部,那样文件会很大
- 能升级到高版本的http就升级到高版本(这个回答是套话),为什么高版本能提高速度具体看上面我说的那篇https文章
- 建立内部CDN能更快速的获取文件
作者:杰杰的风
链接:https://juejin.im/post/6888848660591968264
来源:掘金