Vue 虚拟 DOM 搞不懂?这篇文章帮你彻底搞定虚拟 DOM

您所在的位置:网站首页 vue虚拟dom和真实dom的区别 Vue 虚拟 DOM 搞不懂?这篇文章帮你彻底搞定虚拟 DOM

Vue 虚拟 DOM 搞不懂?这篇文章帮你彻底搞定虚拟 DOM

2023-11-21 12:21| 来源: 网络整理| 查看: 265

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。

前言:本文参与的话题是 Vue 核心知识点和实现原理,话不多说就是搞。

模板转换成视图的过程 Vue.js通过编译将template 模板转换成渲染函数(render ) ,执行渲染函数就可以得到一个虚拟节点树 在对 Model 进行操作的时候,会触发对应 Dep 中的 Watcher 对象。Watcher 对象会调用对应的 update 来修改视图。这个过程主要是将新旧虚拟节点进行差异对比,然后根据对比结果进行DOM操作来更新视图。

虚拟dom.png 我们先对上图几个概念加以解释:

渲染函数:渲染函数是用来生成Virtual DOM的。Vue推荐使用模板来构建我们的应用界面,在底层实现中Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制。 VNode 虚拟节点:它可以代表一个真实的 dom 节点。通过 createElement 方法能将 VNode 渲染成 dom 节点。简单地说,vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。 patch(也叫做patching算法) :虚拟DOM最核心的部分,它可以将vnode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新,其实际作用是在现有DOM上进行修改来实现更新视图的目的。 虚拟DOM 什么是虚拟DOM

Virtual DOM 是一棵以 JavaScript 对象作为基础的树,每一个节点称为 VNode ,用对象属性来描述节点,实际上它是一层对真实 DOM 的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说 Virtual DOM 就是一个 Js 对象,用以描述整个文档。

Item 1 Item 2 { tag: 'ul' attributes: { id: 'myId' } children: [ //这里是 li ] }; 虚拟DOM优势

具备跨平台的优势

由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力。

操作 DOM 慢,js运行效率高,提高效率。

因为DOM操作的执行速度远不如Javascript的运算速度快,因此,把大量的DOM操作搬运到Javascript中,运用patching算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而显著提高性能。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)

提升渲染性能

Virtual DOM的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图进行合理、高效的更新。

虚拟DOM作用

虚拟DOM的最终目标是将虚拟节点渲染到视图上。但是如果直接使用虚拟节点覆盖旧节点的话,会有很多不必要的DOM操作。例如,一个ul标签下很多个li标签,其中只有一个li有变化,这种情况下如果使用新的ul去替代旧的ul,因为这些不必要的DOM操作而造成了性能上的浪费。

为了避免不必要的DOM操作,虚拟DOM在虚拟节点映射到视图的过程中,将虚拟节点与上一次渲染视图所使用的旧虚拟节点(oldVnode)做对比,找出真正需要更新的节点来进行DOM操作,从而避免操作其他无需改动的DOM。

虚拟DOM在Vue.js主要做了两件事:

提供与真实DOM节点所对应的虚拟节点vnode 将虚拟节点vnode和旧虚拟节点oldVnode进行对比,然后更新视图 什么是VNode

Vue.js 利用 createElement 方法创建 VNode。就是描述真实节点的js对象

其实vnode只是一个名字,本质上其实是Javascript中一个普通的对象,是从VNode类实例化的对象。我们用这个Javascript对象来描述一个真实DOM元素的话,那么该DOM元素上的所有属性在VNode这个对象上都存在对应的属性。 vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。例如tag表示一个元素节点的名称,text表示一个文本节点的文本,children表示子节点等。 vnode表示一个真实的DOM元素,所有真实的DOM节点都使用vnode创建并插入到页面中。(vnode-->DOM-->视图) vnode和视图是一一对应的。我们可以把vnode理解成Javascript对象版本的DOM元素。 渲染视图的过程是先创建vnode,然后再使用vnode去生成真实的DOM元素,最后插入页面渲染视图。 VNode的作用

由于每次渲染视图时都是先创建vnode,然后使用它创建真实DOM插入到页面中,所以可以将上一次渲染视图时所创建的vnode缓存起来,之后每当需要重新渲染视图时,将新创建的vnode和上一次缓存的vnode进行对比,查看它们之间有哪些不一样的地方,找出这些不一样的地方并基于此去修改真实的DOM。

VNode属性含义 tag: 当前节点的标签名 data: 当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息 children: 当前节点的子节点,是一个数组 text: 当前节点的文本 elm: 当前虚拟节点对应的真实dom节点 ns: 当前节点的名字空间 context: 当前节点的编译作用域 functionalContext: 函数化组件作用域 key: 节点的key属性,被当作节点的标志,用以优化 componentOptions: 组件的option选项 componentInstance: 当前节点对应的组件的实例 parent: 当前节点的父节点 raw: 简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false isStatic: 是否为静态节点 isRootInsert: 是否作为跟节点插入 isComment: 是否为注释节点 isCloned: 是否为克隆节点 isOnce: 是否有v-once指令

栗子🌰

//html hello,VNode //vnode { tag: 'div' data: { class: 'test' }, children: [ { tag: 'span', data: { class: 'demo' } text: 'hello,VNode' } ] }

Vue.js

vnode.png snaddom

// 编译 h('a', { props: { href: 'http://www.baidu.com' }}, 'Hello word'); // 得到 { "sel": "a", "data": { props: { href: 'http://www.baidu.com' } }, "text": "Hello word" } // 真实的节点 Hello word Vnode的类型

vnode是Javascript中的一个对象,不同类型的vnode之间其实只是属性不同,准确地说是有效属性不同。因为当使用VNode类创建一个vnode时,通过参数为实例设置属性时,无效的属性会默认被赋值为undefined或false。对于vnode身上无效属性,直接忽略就好。

注释节点 export const createEmptyVNode = text =>{ const node = new VNode(); node.text = text; node.isComment = true; return node } //(1)一个注释节点只有两个有效属性-----text和isComment,其余属性全是默认的undefined或则false。 //对应的vnode如下: { text:"注释节点", isComment:true } // 注释节点 文本节点 export function createTextVNode(val){ return new VNode(undefined,undefined,undefined,String(val)) } //(1)文本类型的vnode被创建时,它只有一个text属性 //文本类型的vnode { text:"Hello Berwin" } 克隆节点

(1)克隆节点是将现有节点的属性赋值到新节点中,让新创建的节点和被克隆的节点的属性保持一致,从而实现克隆效果。

(2)作用:优化静态节点和插槽节点(slot node)。

(3)以静态节点为例,当组件内的某个状态发生变化后,当前组件会通过虚拟DOM重新渲染视图,静态节点因为它的内容不会改变,所以除了首次渲染需要执行渲染函数获取vnode之外,后续更新不需要执行渲染函数重新生成vnode。因此,这时就会使用创建克隆节点的方法将vnode克隆一份,使用克隆节点进行渲染。这样就不需要重新执行渲染函数生成新的静态节点的vnode,从而提升一定程度的性能。 (4)克隆现有节点时,只需要将现有节点的属性全部赋值到新节点中即可。

(5)克隆节点和被克隆节点之间的唯一区别是isCloned属性,克隆节点的isCloned为true,被克隆的原始节点的isCloned为false。

export function cloneVNode(vnode,deep){ const cloned = new VNode( vnode.tag, vnode.data, vnode.children, vnode.text, vnode.elm, vnode.context, vnode.componentOptions, vnode.asyncFactory ) cloned.ns = vnode.; cloned.isStatic = vnode.isStatic; cloned.key = vnode.key; cloned.isComment = vnode.isComment; cloned.isCloned = true; if(deep&&vnode.children){ cloned.chlidren = cloneVNodes(vnode.children); } return cloned; } 元素节点

(1)元素节点通常存在以下4种有效属性。

tag:tag是一个节点的名称,例如ul、p、li和div等。 ​ data:该属性包含了一些节点上的数据,比如attrs、class和style等。 ​ children:当前节点的子节点列表。 ​ context:它是当前组件的Vue.js实例。

// 真实的元素节点

HelloBerwin

// 对应的vnode { children:[VNode,VNode], context:{...}, data:{...}, tag:"p", .... } 组件节点

componentOptions:组件节点的选项参数,其中包含propsData、tag和children等信息。 componentInstance:组件的实例,也就是Vue.js实例。事实上,在Vue.js种,每个组件都是一个Vue.js实例。

// 一个组件节点 // 对应的vnode如下: { componentInstance:{...}, componentOptions:{...}, context:{...}, data:{...}, tag:"vue-component-1-child", .... } 函数式组件

函数式组件和组件节点类似,它有两个独有的属性functionalContext和functionalOptions。

{ functionalContext:{...}, functionalOptions:{...}, context:{...}, data:{...}, tag:"div", .... } Patch updateChildren

image.png oldStartIdx、newStartIdx、oldEndIdx以及newEndIdx分别是新老两个VNode两边的索引,同时oldStartVnode、newStartVnode、oldEndVnode和new EndVnode分别指向这几个索引对应的vnode。整个遍历需要在oldStartIdx小于oldEndIdx并且newStartIdx小于newEndIdx(这里为了简便,称sameVnode为相似)。

当oldStartVnode不存在的时候,oldStartVnode向右移动,oldStartIdx加1。 当oldEndVnode不存在的时候,oldEndVnode向右移动,oldEndIdx减1。 oldStartVnode和newStartVnode相似,oldStartVnode和newStartVnode都向右移动,oldStartIdx和newStartIdx都增加1。

image.png

oldEndVnode和newEndVnode相似,oldEndVnode和newEndVnode都向左移动,oldEndIdx和newEndIdx都减1。

image.png

oldStartVnode和newEndVnode相似,则把oldStartVnode.elm移动到oldEndVnode.elm的节点后面。然后oldStartIdx向后移动一位,newEndIdx向前移动一位。

image.png

oldEndVnode和newStartVnode相似时,把oldEndVnode.elm插入到oldStartVnode.elm前面。同样的,oldEndIdx向前移动一位,newStartIdx向后移动一位。

image.png 当以上情况都不符合的时候。

生成一个key与旧vnode对应的哈希表。

//createKeyToOldIdx 函数,该函数的作用是 当 对比两个子节点数组时,建立 key-index映射代理遍历查找 sameNode.提高性能。 function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i newEndIdx,就说明还存在新节点,就将这些节点进行删除。

点赞支持、手留余香、与有荣焉,动动你发财的小手哟,感谢各位大佬能留下您的足迹。

往期精彩推荐

前端性能优化实战

聊聊让人头疼的正则表达式

获取文件blob流地址实现下载功能

Git 相关推荐

通俗易懂的 Git 入门

git 实现自动推送

我在工作中是如何使用 git 的

面试相关推荐

前端万字面经——基础篇

前端万字面积——进阶篇

node系列教学及更多精彩详见:个人主页



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3