从虚拟dom知识无痛深入vue与react的原理

 我们都知道像vue、react都有用到虚拟dom,那么虚拟dom到底是什么?框架为什么不直接操作真实dom而要在中间要引入虚拟dom呢?vue和react的虚拟dom又有什么异同呢?我们就从虚拟dom开始讲起,再来逐步引入讲解vue与react的部分原理及其异同,这里会顺便讲解到数据驱动视图及视图驱动数据,顺便明白MVVM、MVC这两种模式,顺便引入相关的八股文知识点,好好阅读这篇文章你会不小心一下子就明白了一大堆知识Σ(っ °Д °;)っ

所谓的虚拟dom、真实dom到底是什么?

实际上,我们在html文件里写的标签即为真实dom(能直接被浏览器解析渲染在页面呈现相应元素)。而虚拟 dom 就是一个普通的 js 对象,这个对象描述了界面的渲染内容虚拟dom与真实dom可以一一对应。如图:

//真实dom
<div id="app">
  <p class="text">木鱼</p>
</div>
//虚拟dom本质就是一个js对象,用来描述界面渲染内容,上述真实dom对应的虚拟dom结构如下:
{
  tag: 'div',
  props: {
    id: 'app'
  },
  chidren: [
    {
      tag: 'p',
      props: {
        className: 'text'
      },
      chidren: [
        '木鱼'
      ]
    }
  ]
}

虚拟dom的作用:

如果没有虚拟dom:作为通用框架,很难去预知数据跟界面之间到底是如何对应的(比如一个数据变了,框架不知道是界面哪个区域发生变动,数据是数据,界面是界面,框架能修改数据,也能通过render函数让界面重新渲染,但框架无法确定数据影响界面的具体区域),所以在没有虚拟 dom 的情况下,框架只能在数据发生变化了就直接将整个界面重新生成一次,这样没有发生变化的元素也会重新生成一遍,如此操作真实 dom 的代价就非常昂贵,会触发到浏览器的重排重绘让浏览器对整个页面所有元素都重新渲染,就会严重引发效率问题。

为了减少对真实 dom 无意义的操作,因此不得不引入虚拟 dom。当数据变化之后,由于框架无法知道数据与界面的一一对应关系,不知道界面哪个区域要更新,而全量生成真实dom导致的渲染代价较大,既然要全量生成,框架就选择全量生成虚拟 dom(虚拟dom是 js 对象,给 js 对象属性赋值修改属性值、改动对象结构,并不涉及界面,仅仅只是修改一个对象属性而已,效率自然是非常高的),然后通过跟之前的虚拟 dom 进行对比,找到有差异的虚拟dom节点,改动该虚拟节点所对应的真实 dom即可(注意前面提过虚拟dom与真实dom可以一一对应,如此,真实dom就无需全量生成渲染,框架仅需根据虚拟dom对比后的结果做对应的节点更新即可。

除了以上情况,框架引入虚拟dom还有第二个原因,就是建立了一个抽象层。特别是像 react 这种框架,它在设计之时并没有将自己定位为一个页面级别应用框架,而是把自己定位为一个 UI 库(UI 表示用户界面,而用户界面不一定是网页,也可能是移动端、桌面应用程序等,而不同平台差异很大,我们平时说的 dom 通常是指在页面应用中的,而像小程序、移动端 app、桌面应用都没有所谓的 dom),框架为了消除平台之间的差异,于是抽象了一个UI的表达方式,它使用一个普通对象(即虚拟 dom)来表达 UI 界面,然后根据不同的平台去具体生成真实的界面,即建立了一个抽象层,也就是用一套虚拟 dom,可以对接不同平台的渲染逻辑,实现一次编码多端运行

虚拟dom树在框架中的存在形式

vue中的虚拟dom

  • vue 是怎么得到虚拟 dom 树的呢?
  • 实际上是 vue 有配置一个 render 函数,vue 运行了 render 函数返回的结果就是虚拟 dom(这个 render 函数有一个参数 h,h是个函数,h传入一个代表标签名和标签内容的参数,即可生成一个虚拟 dom,标签内容可再继续嵌入 h 函数表示其子节点信息)当改动数据后,render 依赖了这个被 vue 监听的,则会再执行一次 render 函数响应式,具体原理我在这篇文章描述过《关于Vue的数据响应式》原文链接:http://t.csdnimg.cn/KYlEg
  • 而 vue 给我们提供了模板,我们在Vue上是通过Vue的模板(在template标签或属性里写)书写HTML信息,而vue模板上的内容实际上并不是真实 dom,vue 会根据模板编译成 render 方法逻辑是看是否有 render 方法,有的话则略过模板内容直接把 render 方法的返回结果作为虚拟节点树。如果没有 render 方法,则查找模板,即 template 这个配置,把模板编译成 render 方法;如果模板 template 这个配置也没有的话,它会查找 el 这个配置对应的元素,将这个元素直接作为模板,再将模板编译成 render,再通过 render 生成虚拟节点树,才能进行渲染真实节点。由于树形结构只有一个根节点(一个根节点才能保证是一棵树),因此 vue 会要求模板也必须只有个根节点,这样编译成 render 函数生成的虚拟节点数才是单根的。

react中的虚拟dom

  • react 又是怎么得到虚拟 dom 树的呢?
  • 无论是使用react 的 class 或 function 写法, 本质都是生成 render 函数,其返回结果为描述页面信息结构的jsx语法。
  • react 是使用 jsx 语法,在组件中直接编写虚拟 dom 结构,然后通过 Babel 等工具将其转换为 JavaScript 代码。
  • jsx其实是 createElement()方法的语法糖,jsx 语法会被@babel/preset-react 插件编译为 createElement()方法,而 createElement()方法会返回 react 元素,也就是虚拟dom对象(react 元素就是一个 js 对象,用来描述真实 dom 的内容,其本质也就是虚拟 dom,只是在不同应用场景叫法不同)

vue与react的虚拟dom及其应用形式的对比

本质上vue与react虚拟dom并没有什么区别。vue 和 react 本质都是用 js 对象来模拟真实 dom(二者仅仅是让程序员书写页面结构的形式以及将其编译为虚拟dom的方式不同。但无论是 vue 的模板写法还是 react 的 class 或 function 写法, 最后都是生成 render 函数,而 render 函数会执行返回一个描述页面信息结构的对象,也就是虚拟 dom,本质上是棵树),然后都是通过对虚拟 dom 进行 diff 算法来最小化更新真实 dom,从而减少不必要的性能损耗(其中 diff 算法的优化思路基本相同:tag 不同则视为不同节点;只比较同一层级,不跨级比较;同一层级节点用 key 唯一标识,tag 和 key 都相同则视为同一节点[所以无论是 react 还是 vue,在写动态列表时都需要设置一个唯一值 key,这样 diff 算法处理时才能性能最大化]

👇而vue与react 在应用虚拟dom的具体实现上又不尽相同:

vue和react对dom的更新策略不同(数据响应式原理不同)

扫盲解释→对dom的更新策略和虚拟dom的关系:vue与react在响应式数据变动后会对dom进行更新,这个更新过程会生成虚拟dom然后进行diff算法后再更新真实dom。而这里的更新策略指的是数据变化后是怎么让dom更新的,通过虚拟dom进行更新没毛病,不过在数据变化后到生成虚拟dom的这个过程是怎样的呢?vue和react是各有一套策略的。

  •  vue 会通过 Object.defineProperty 把 data 属性的值都转化为监听状态的,当这些被监听的数据的 setter 被调用时,则会重新执行依赖到这些数据的函数,使得组件关联更新(具体原理可看之前写过的这两篇文章1.《从根源理解Proxy对比defineProperty优势》原文链接:http://t.csdnimg.cn/tfPtP;2.《关于Vue的数据响应式》原文链接:http://t.csdnimg.cn/KYlEg )。
  • react 需用 setState 驱动新的 state 替换旧的 state,当数据改变时以组件为根目录默认全部重新渲染

diff 算法源码实现不同:

  • vue 通过跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。 vue 的diff算法使用双向链表,边对比边更新 dom
  • react 会对虚拟 dom 自顶向下全进行 diff 算法,重新生成新的虚拟 dom 树,新旧虚拟 dom 进行对比,得到 patch 树,再统一操作批量更新 dom(进行 patch 打补丁方式局部更新 dom),react会以组件为根目录默认全部重新渲染所以 react 为避免父组件更新引起不必要的子组件更新,可以在 shouldComponentUpdate 做逻辑判断,减少不必要的 render 重新生成虚拟 dom

讲完了vue与react的虚拟dom的存在形式及应用形式,这里也提到了vue与react对dom的更新策略,实际上对dom的更新策略,也就是数据驱动视图的过程。那么接下来趁热打铁,我们再来讲一下数据驱动视图这一回事,进一步深入理解vue与react的原理。

数据驱动视图

数据驱动视图,就是数据变化的时候相应的视图会得到更新,开发者仅需要关注数据的变化,不用再去手动操作 dom。
vue 的数据驱动是通过MVVM这种体系实现的。MVVM 指 Model(数据部分,对应到前端就是 JavaScript 对象)、View(视图部分。对应前端就是真实 dom)、ViewModel(连接视图与数据的中间件),viewmodel 是实现数据驱动视图的核心,即利用 object.defineProperty 或 proxy 实现数据监听,监听的数据变化了则从 setter 中将依赖到该数据的函数重新执行一遍,包括 render 则重新执行,页面则也重新渲染,这样就完成数据响应式(具体可看 1.《从根源理解Proxy对比defineProperty优势》原文链接:http://t.csdnimg.cn/tfPtP;2.《关于Vue的数据响应式》原文链接:http://t.csdnimg.cn/KYlEg ),如此便建立了视图与数据之间的联系,实现了所谓的数据对于视图的驱动。

vue的MVVM体系还实现了视图驱动数据的更改(v-model):通过监听页面中表单元素内容的变化,自动去修改相关数据。因此vue是“双向驱动”的。

react 采用的是MVC体系(M是Model数据部分;V是View视图部分;C是Controller控制器)。react主要专注于View层,react会根据初始 state(Model)创建一个虚拟 DOM 对象,根据虚拟 dom 生成真实 dom 渲染到页面(View)中。 用户在视图上操作触发使数据变更的事件、react根据新的数据重新渲染则是发生在控制层(Controller)。而react的数据驱动视图则是通过 setState 实现(setState 做了两件事,一件事更新 state,一件是重新引发组件的更新),通过 setState 变更数据后重新调用了一次 render 函数,render 函数会再创建新的虚拟 dom 树, 通过 diff 算法对比后再将发生变更的组件进行重新渲染(变更的组件会作为根目录带动子组件一同渲染,可通过给没有发生数据变化的子组件设置 shouldComponentUpdate 返回 false来进一步减少不必要的重新渲染)。

react并没有实现视图驱动数据。react视图层中的表单内容改变,想要修改数据,需要开发者自己写代码去实现(将表单的value属性手动赋值给一个状态值a,监听表单元素的change事件,通过onChange获取最新表单内容,再手动修改状态值a为其最新表单内容,类似于Vue中的v-model原理自己手动实现一遍)。因此react是“单项驱动”的。

结合前面所讲的vue与react虚拟dom表现形式及数据驱动视图的异同,我们再来总结一下这两个框架运作的原理(下划线即为两个框架运作过程的共同之处,彩色字体为二者的主要区别):

  • vue通过一定逻辑渠道拿到render方法,如模板编译成render方法(具体获取render的逻辑看前面紫色字体的部分),通过运行render函数返回的结果拿到虚拟dom,虚拟dom再应用于真实dom实现页面的初步渲染; vue基于以object.defineProperty 或 proxy为核心原理实现数据响应式,当响应式数据发生变动后会重新调用有依赖到该数据的函数,包括render函数,重新生成虚拟dom树,通过diff算法将新旧两棵虚拟dom树进行对比找出有差异的节点,再将发生变动的虚拟节点应用的真实dom中只更新依赖到响应式数据的节点)。
  • react通过Babel等工具将render函数返回的jsx结果编译为cteateElement()方法,该方法返回结果即为虚拟dom,虚拟dom再应用于真实dom实现页面的初步渲染;react通过setState实现数据驱动视图,当数据通过setState发生变更后,setState也会引发组件的更新,重新调用了一次 render 函数,使render 函数创建新的虚拟 dom 树, 通过 diff 算法对比后再以发生变更的组件为根目录进行重新渲染(除非 shouldComponentUpdate 返回 false)。

相关八股文知识点

  • React 性能优化是哪个周期函数? 参考回答:shouldComponentUpdate。这个方法用来判断是否需要调用 render 方法重新描绘 dom。因为在发生数据变更时,react会以变更组件为根目录进行重新渲染,为避免父组件更新引起不必要的子组件更新,或者某个组件重新渲染后跟原来其实一样,如果我们能在shouldComponentUpdate 方法中写出更优化的 dom diff 算法,如加入state和props没有变化时就不去渲染组件的逻辑判断,则可以省去diff操作,避免不必要的渲染,可以极大的提高性能。
  • React的工作原理?参考回答:React 会通过编译jsx语法生成一个虚拟dom。通过setState实现数据驱动视图,当一个组件中的状态改变时,React 会再生成一个新的虚拟dom,通过 diff算法来标记虚拟 DOM 中的改变,再将虚拟dom应用于真实dom中,以发生变更的组件为根目录进行重新渲染。(原版答案更简略,这参考答案我稍微再自行再加工了,但能像我前面介绍的那样说得越全越深入当然越好啦~)
  • React性能优化方案?参考回答:1.重写shouldComponentUpdate来避免不必要的dom操作;2.给列表结果的每个单元添加唯一的key属性便于diff算法过程的比较;3.使用production版本的React.js。
  • 当渲染一个列表时,为何设置key值?参考回答:keys有助于react或vue在进行diff算法过程中识别哪些items的增删改变动。keys应被赋予数组里的元素以赋予DOM元素一个稳定的标识,选择一个key的最佳方法是使用字符串唯一标识一个列表项(如常用数据中的IDs作为keys),当没有稳定的ids用于被渲染的items时,可以使用项目索引作为渲染项的key值,但这种方式不推荐,因为如果列表可以重新排序则索引将不再对应原来的节点,这样就会导致很多没有变化的节点因key值发生改变,在diff中对应不上key值而重新渲染。
  • 何为JSX?参考回答:JSX是JavaScript语法的一种语法扩展,并拥有 JavaScript 的全部功能。你可以将任何的 JavaScript 表达式封装在花括号里,然后将其嵌入到 JSX中,在编译完成之后,JSX 会被转化为普通的 JavaScript 对象,所以可以在if 语句和 for 循环内部使用 JSX,将它赋值给变量,当作参数传入,作为返回值也都是允许的。JSX 也可以看成是React.createElement()的语法糖,它可以被babel等工具编译生成React元素,也就是虚拟dom。
  • 什么是虚拟dom?为什么虚拟dom能提高性能?参考回答:由于框架不知道数据与界面的一一对应关系,如果数据变了就直接重新渲染整个页面将引发严重的效率问题。为减少对真实dom无意义的操作,框架引入了虚拟dom。虚拟dom是一个表示界面渲染信息的JavaScript对象,而查找 js 对象的属性变化要比查询真实 dom 树的性能开销小,当数据发生变化时,框架会重新构建一棵新的虚拟dom树,通过对比新旧两棵虚拟dom树,找出差异节点,再应用到相应的真实dom中进行更新,也能避免了没必要更新的真实dom被重复渲染,从而提高了性能。(如果问虚拟dom的作用,还要再加一个:框架通过虚拟dom建立了一个抽象层,用一套虚拟 dom,可以对接不同平台的渲染逻辑,实现一次编码多端运行)。
  • Vue双向绑定的原理?参考回答:Vue数据双向绑定(v-model)是通过数据劫持/数据监听结合发布者-订阅者模式的方式来实现的。v-model实际上是一则语法糖,相当于通过v-bind:value="xxx"绑定了一个value属性的值,子组件可对value属性进行数据监听,其中数据监听是利用了Object.defineProperty()这个方法重新定义了对象获取属性值(getter)和设置属性值(setter)实现的。然后再通过$emit('input',xxx)的方式给父组件通信,这样就实现双向数据绑定了。
  • 43
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: VueReact虚拟DOM有一些不同之处,尽管它们的目的都是提高渲染性能和抽象渲染过程。 Vue使用双向数据绑定来实现虚拟DOM,这意味着当应用数据发生更改时,Vue会自动更新虚拟DOM以反映数据的变化,并且当用户更改界面时,Vue也会将这些更改同步到应用数据中。 React使用单向数据流来实现虚拟DOM,这意味着React只关注如何渲染虚拟DOM,而不关注如何将这些更改同步回应用数据中。当应用数据发生变化时,React会重新渲染虚拟DOM,以确保界面与应用数据同步。 总的来说,Vue虚拟DOM实现更加方便和直观,但React的实现更加灵活,可以提供更多的控制和定制选项。 ### 回答2: VueReact都采用了虚拟DOM(Virtual DOM)作为其底层实现之一,用以提高页面渲染的效率。虚拟DOM是一个轻量级的内存对象树结构,用于表示网页的DOM结构。它在实际的DOM更新之前,将所有的变更以最小的成本应用到虚拟DOM中,然后再与实际的DOM进行对比,找出需要更新的节点并进行相应的操作,以降低DOM操作的成本。 尽管VueReact都使用了虚拟DOM,但它们在实际的实现上有一些不同之处: 1. 更新策略:Vue使用了双向绑定的机制,当数据发生变化时,它会自动更新相关的DOM节点。而React采用了单向数据流的原则,即数据的变化只能由上至下,从组件的父节点传递到子节点,当数据变化时,需要手动更新组件。因此在Vue中,虚拟DOM的更新更加自动化,而React需要手动管理虚拟DOM的更新。 2. 组件实现:Vue中的组件是通过配置对象来定义的,这使得组件可以在内部定义自己的模板和逻辑。而React则使用了JSX语法来定义组件的结构,JSX允许在JS代码中编写类似XML的结构。因此在Vue的组件中,虚拟DOM的实现更加灵活,可以在组件内部自由组织结构和逻辑。 3. 性能优化:Vue通过侦听数据的变化来自动更新虚拟DOM,而React则通过Diff算法来高效地计算出需要更新的节点。Vue在数据量较小的情况下具有更高的性能,但在数据量较大的情况下,React的Diff算法相对更为高效。 综上所述,VueReact虚拟DOM在更新策略、组件实现和性能优化等方面存在一些差异。选择Vue还是React,最终要根据实际项目需求和个人喜好来决定。 ### 回答3: VueReact都使用了虚拟DOM(Virtual DOM)来优化页面的渲染效率,但它们在实现细节上有一些不同。 首先是更新机制的不同。在Vue中,每个组件都有自己的虚拟DOM树,当组件状态变化时,Vue会通过比较前后两颗虚拟DOM树的差异来更新真实的DOM。而在React中,所有组件共享同一个虚拟DOM树,当组件状态变化时,React会通过比较前后两个虚拟DOM树的差异来更新真实的DOM。 其次是数据绑定的不同。在Vue中,可以使用双向数据绑定,即当数据发生变化时,视图会自动更新;而在React中,数据的变化只能通过显式的改变状态来触发更新,没有Vue中的自动更新机制。 另外,Vue虚拟DOM中使用了一些特殊技术来优化性能,如模板编译、静态节点优化、异步渲染等。而React虚拟DOM则相对简单,更加灵活,可以配合各种工具和库进行更多的自定义操作。 最后,VueReact在使用上也有差异。Vue通常使用单文件组件的形式,将HTML、CSS和JavaScript写在同一个文件中,更加便于编写和维护;而React则更加灵活,可以与其他工具和库进行组合使用。 综上所述,虽然VueReact都使用了虚拟DOM来提高性能,但它们在实现细节和使用上存在一些区别。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值