「深入浅出」主流前端框架更新批处理方式

共 8290字,需浏览 17分钟

 ·

2022-03-09 13:06

一 背景

大家好,我是 alien ,一提到更新,是前端框架中一个老生常谈的问题,这些知识也是在面试中,面试官比较喜欢问的,那么在不同的技术框架背景下,处理更新的手段各不相同,今天我们来探讨一下,主流的前端框架批量处理的方式,和其内部的实现原理。

通过今天的学习,你将收获这些内容:

  •  主流前端框架的批量更新方式。
  •  vue 和 react 批量更新的实现。
  •  宏任务和微任务的特点。

1 一次 vue 案例

首先来想一个问题。比如在 vue 中一次更新中。

<template>
   <div>
       姓名: {{ name }}
       年龄: {{ age }}
       <button @click="handleClick" >点击button>
   div>
template>

<script>
export default {
    data(){
        return {
            age:0,
            name:''
        }
    },
    methods:{
        handleClick(){
            this.name = 'alien'
            this.age = 18
        }
    }
}
script>

如上是一个非常简单的逻辑代码,点击按钮,会触发 nameage 的更新。那么首先想一个问题就是:

  • 正常情况下,vue 的数据层是通过响应式处理的,那么比如 age 和 name 可以理解成做了一层属性代理,字符串模版 template 里面的属性 ( name 和 age ) 的 get 会和组件的渲染 watcher ( vue3.0 里面的 effect )建立起关联。

  • 一次重新赋值会触发 set ,那么根据响应式,会触发渲染 watcher 重新执行,然后就会重新更新组件,渲染视图。

那么暴露的问题就是,我们在 handleClick 中,同时改变了 name 和 age 属性,那么按照正常情况下,会分别触发 name 和 age 的 set,那么如果不做处理,那么会让渲染 watcher 执行两次,结果就是组件会 update 两次,但是结果是这样的吗?

结果是:vue 底层通过批量处理,只让组件 update 一次。

2 一次 react 案例

上面介绍了在 vue 中更新批处理的案例之后,我们来看一下在 react 中的批量更新处理。把上述案例用 react 来实现一下:

function Index(){
    const [ age , setAge ] = React.useState(0)
    const [ name, setName ] = React.useState('')
    return <div>
       姓名: {name}
       年龄: {age}
       <button onClick={()=>{
          setAge(18)
          setName('alien')
       }}
       >点击button>

    div>
}

点击按钮,触发更新,会触发两次 useState 的更新函数。那么 React 的更新流程大致是这样的。

  • 首先会找到 fiberRoot 。
  • 然后进行调和流程。执行 Index 组件,得到新的 element。
  • diff fiber,得到 effectList。
  • 执行 effect list,得到最新的 dom ,并进行渲染绘制。

那么按常理来说,Index 组件会执行两次。可事实是只执行一次 render。

3 批量处理意义

通过上面的案例说明在主流框架中,对于更新都采用批处理。一次上下文中的 update 会被合并成一次更新。那么为什么要进行更新批处理呢?

批处理主要是出于对性能方面的考虑,这里拿 react 为例子,看一下批处理前后的对比情况:

🌰例子一:假设没有批量更新:

/ ------ js 层面 ------

  • 第一步:发生点击事件触发一次宏任务。
  • 第二步:执行 setAge ,更新 fiber 状态。
  • 第三步:进行 render 阶段,Index 执行,得到新的 element。得到 effectlist.
  • 第四步:进行 commit 阶段,更新 dom。
  • 第五步:执行 setName ,更新 fiber 状态。
  • 第六步:重复执行第三步,第四步。

/ ------ 浏览器渲染 ------

  • js 执行完毕,渲染真实的 dom 元素。

我们可以看到如果没有批量更新处理,那么会多走很多步骤,包括 render 阶段 ,commit 阶段,dom 的更新等,这些都会造成性能的浪费,接下来看一下有批量更新的情况。

🌰例子二:存在批量更新。

/ ------ js 层面 ------

  • 第一步:发生点击事件触发一次宏任务。
  • 第二步:setAge 和 setName 批量处理 ,更新 fiber 状态。
  • 第三步:进行 render 阶段,Index 执行,得到新的 element。得到 effectlist.
  • 第四步:进行 commit 阶段,更新 dom。

/ ------ 浏览器渲染 ------

  • js 执行完毕,渲染真实的 dom 元素。

从上面可以直观看到更新批处理的作用了,本质上在 js 的执行上下文上优化了很多步骤,减少性能开销。

二 简述宏任务和微任务

在正式讲批量更新之前,先来温习一下宏任务微任务,这应该算是前端工程师必须掌握的知识点。

所谓宏任务,我们可以理解成,