从输入 URL 到页面加载完成,完整的链路

 

http层面优化

  • DNS 解析:
    DNS 实现域名到IP的映射。通过域名访问站点,每次请求都要做DNS解析。目前每次DNS解析,通常在200ms以下。一般采用DNS Prefetch 一种DNS 预解析技术,当你浏览网页时,浏览器会在加载网页时对网页中的域名进行解析缓存,这样在你单击当前网页中的连接时就无需进行DNS的解析,减少用户等待时间,提高用户体验。
<link rel="dns-prefetch" href="www.baidu.com" />
只有部分浏览器支持
  • TCP 连接:
    采用http2.0,可以复用tcp通道,采用二进制格式而非文本格式,使用报头压缩,HTTP/2降低了开销,支持cache push

  • 浏览器并发
    基于端口跟线程切换开销,浏览器不可能无限的并发请求。chrome的并发为6,超过限制数目的请求就会被阻塞;
    对于某些静态资源,图片等等,我们可以对其URL分散处理 ,不同的资源域名(部署在cdn上)。

  • http请求次数
    减少http的请求次数,将多个请求合并成同一个,减少http的开销

  • webpack
    充分利用webpack提供给我们的能力,利用DllPlugin与commonPlugins等插件对我们代码进行
    优化,文件的分割与合并,公共代码的提取,长缓存等策略,webpack是个很好的东西,值得大家仔细研究

  • http压缩
    采用Gzip压缩:HTTP 压缩就是以缩小体积为目的,对 HTTP 内容进行重新编码的过程,原理是找出一些重复出现的字符串、临时替换它们,从而使整个文件变小,文件中代码的重复率越高,那么压缩的效率就越高,使用 Gzip 的收益也就越大

浏览器渲染

浏览器渲染机制

    • DOM树:
      解析 HTML 以创建的是 DOM 树(DOM tree ):渲染引擎开始解析 HTML 文档,转换树中的标签到 DOM 节点,它被称为“内容树”。
    • CSSOM树:
      解析 CSS(包括外部 CSS 文件和样式元素)创建的是 CSSOM 树。CSSOM 的解析过程与 DOM 的解析过程是并行的。
      -渲染树:
      CSSOM 与 DOM 结合,之后我们得到的就是渲染树(Render tree )。
    • 布局渲染树:
      从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标,我们便得到了基于渲染树的布局渲染树(Layout of the render tree)。
    • 绘制渲染树:
      遍历渲染树,每个节点将使用 UI 后端层来绘制。整个过程叫做绘制渲染树(Painting the render tree)。

当我们浏览器获得HTML文件后,会自上而下的加载,并在加载过程中进行解析和渲染。

加载说的就是获取资源文件的过程,如果在加载过程中遇到外部CSS文件和图片,浏览器会另外发送一个请求,去获取CSS文件和相应的图片,这个请求是异步的,并不会影响HTML文件的加载。

但是如果遇到Javascript文件,HTML文件会挂起渲染的进程,等待JavaScript文件加载完毕后,再继续进行渲染。为什么HTML需要等待JavaScript呢?因为JavaScript可能会修改DOM,导致后续HTML资源白白加载,所以HTML必须等待JavaScript文件加载完毕后,再继续渲染,这也就是为什么JavaScript文件在写在底部body标签前的原因。

 

  重绘与重排

当DOM的变化引发了元素几何属性的变化,比如改变元素的宽高元素的位置,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。

简单的说,重排负责元素的几何属性更新,重绘负责元素的样式更新。而且,重排必然带来重绘,但是重绘未必带来重排。比如,改变某个元素的背景,这个就不涉及元素的几何属性,所以只发生重绘。

上面已经提到了,重排发生的根本原理就是元素的几何属性发生了改变,那么我们就从能够改变元素几何属性的角度入手

 重排触发几种情景:

  1. 添加或删除可见的DOM元素
  2. 元素位置改变
  3. 元素本身的尺寸发生改变
  4. 内容改变
  5. 页面渲染器初始化
  6. 浏览器窗口大小发生改变

 如何进行性能优化针对重排

重绘和重排的开销是非常昂贵的,如果我们不停的在改变页面的布局,就会造成浏览器耗费大量的开销在进行页面的计算,这样的话,我们页面在用户使用起来,就会出现明显的卡顿。现在的浏览器其实已经对重排进行了优化,比如如下代码:

比较久远的浏览器,这段代码会触发页面2次重排,在分别设置宽高的时候,触发2次.

当代的浏览器对此进行了优化,这种思路类似于现在流行的MVVM框架使用的虚拟DOM,对改变的DOM节点进行依赖收集,确认没有改变的节点,就进行一次更新。但是浏览器针对重排的优化虽然思路和虚拟DOM接近,但是还是有本质的区别。大多数浏览器通过队列化修改并批量执行来优化重排过程。也就是说上面那段代码其实在现在的浏览器优化下,只构成一次重排。

但是还是有一些特殊的元素几何属性会造成这种优化失效。比如:

 为什么造成优化失效呢?仔细看这些属性,都是需要实时回馈给用户的几何属性或者是布局属性,当然不能再依靠浏览器的优化,因此浏览器不得不立即执行渲染队列中的“待处理变化”,并随之触发重排返回正确的值。

最小化重绘和重排

 既然重排&重绘是会影响页面的性能,尤其是糟糕的JS代码更会将重排带来的性能问题放大。既然如此,我们首先想到的就是减少重排重绘。

批量修改DOM

批量修改DOM元素的核心思想是:

  • 让该元素脱离文档流
  • 对其进行多重改变
  • 将元素带回文档中

优化技巧:
1.DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。

2.不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式。

3.尽量使用离线DOM,而不是真实的网面DOM,来改变元素样式。比如,DocumentFragment,或 cloneNode()

4.也可以先 display: none ,然后随便操作,最后再恢复显示

5.position属性为absolute或fixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。

6.还可以考虑,“只在必要的时候,才将元素的display属性为可见”

7.使用虚拟DOM(vue, react)

8.window.requestAnimationFrame() 方法,就个是“节流”的思想,将代码放到下一次重新渲染时执行。页面滚动事件(scroll)的监听函数,还有网页动画,就很适合用 window.requestAnimationFrame()

还有另一个,window.requestIdleCallback(),把函数放到浏览器的空闲时段内调用

9.如果你已经知道图片的宽高了,你最好是写在内联样式上。
这样图片在下载完之前,浏览器会根据你的宽高占一个位置,如果没有宽高,图片下载下来后,整个页面会发生重排。

10.缓存布局信息
11.避免一些无用数据,减少重排次数
 

多余的的重排

vue方面的一些例子

1、v-if 和 v-show 区分使用场景
  v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
  v-show 就简单得多, 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行切换。
  所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
2、computed 和 watch 区分使用场景
  computed:**是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
  watch:**更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
  运用场景:
  当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。


3、利用冻结数据
  Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

4、事件的销毁
  Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 如果在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露,如:

created() {
  addEventListener('click', this.click, false)
},
beforeDestroy() {
  removeEventListener('click', this.click, false)
}

5.scope中元素选择器尽量少用

用途:防止全局同名CSS污染
原理:在标签加上v-data-something属性,再在选择器时加上对应[v-data-something],即CSS带属性选择器,以此完成类似作用域的选择方式

缺点:
(1)由于只是通过属性限制,类还是原来的类,所以在其他地方对类设置样式还是可以造成污染。
(2)添加了属性选择器,对于CSS选择器的权重加重了。
(3)外层组件包裹子组件,会给子组件的根节点添加data属性。在外层组件中无法修改子组件中除了根节点以外的节点的样式。比如子组件中有box类,在父节点中设置样式,会被编译为

.box[data-v-x]

 

 

12-28 20:08
查看更多