目录
- 由状态到UI
- 状态
- 渲染
- 命令式操作DOM
- 声明式操作DOM
- 效率的取舍
- 虚拟DOM
- VNode
- Patch
由状态到UI
状态
状态可以是JavaScript中的任意类型。Object、Array、String、Number、Boolean等都可以作为状态,这些状态可能最终会以段落、表单、链接或按钮等元素呈现在用户界面上,具体地说是呈现在页面上
渲染
本质上,我们将状态作为输入,并生成DOM输出到页面上显示出来,这个过程叫作渲染
在Web开发的早期,因为网页的展现十分简陋,我们所维护的状态也十分的简单,我们大可以直接操作DOM来进行开发,然而随着时代的发展,我们所开发的web也变的十分复杂,维护的状态也十分的多,DOM操作也十分频繁
命令式操作DOM
随着开发的深入,我们会发现在我们的项目中存在着大量用于操作DOM的代码,这种由我们直接通过代码来操作DOM的开发方法被称为命令式操作DOM
这些代码难以复用,所用到的状态也很难管理,会导致整个项目的逻辑变得十分混乱
声明式操作DOM
幸运的是,现代的主流框架,如vue,react,angular等都支持声明式操作DOM
声明式操作DOM即我们只用声明状态,通过模板或者其他方式来描述状态与视图的关系,框架就能帮我们渲染视图
自此,我们不再需要手动的操作DOM,统一由框架来操作DOM也能获得更好地渲染效率
事实上,并不是因为框架的出现web才有了状态,任何应用本就具有状态,只不过现代框架揭露了一个事实:我们的精力应该放在状态上,而不是如何将状态变成视图
效率的取舍
框架的设计处处充斥着权衡的艺术
假设我们将直接操作DOM的性能损耗设为A
div.innerHTML = "hello world"
那么通过声明式操作DOM的性能损耗为A+B
<div>{{ hello world }}</div>
因为框架不仅需要直接修改,而且还需要找出差异,所以声明式的代码性能并不优于命令式代码,但声明式代码的可维护性高,效果直观,而且我们可以通过各种手段来提升声明式代码的性能
但是需要注意的是,声明式代码本就依托于命令式代码,所以声明式代码优化也只能在寻找差异步骤处优化,再怎么优化性能也不会比命令式代码性能高效
虚拟DOM
理论上,用户的任何操作都有可能造成状态的改变,当状态改变时我们就需要重新渲染,直接操作DOM向来是简单且直接的,我们大可以将现有的DOM直接删除,然后重新根据现有状态来生成一个DOM,但是访问DOM是非常昂贵的。按照上面说的方式做,会造成相当多的性能浪费。状态变化通常只有有限的几个节点需要重新渲染,所以我们不仅需要找出哪里需要更新,还需要尽可能少地访问DOM
如何在找出哪里需要更新并且最大限度的减少DOM的访问,各家框架都有自己的一套解决方案,angular是脏检查的流程,react是虚拟DOM,vue1是细粒度的绑定,但从vue2开始就变成了虚拟DOM
虚拟DOM的解决方式是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染。在渲染之前,会使用新生成的虚拟节点树和上一次生成的虚拟节点树进行对比,只渲染不同的部分
在vue中,我们使用模板来描述状态与DOM之间的映射关系。vue通过编译将模板转换成渲染函数(render),执行渲染函数就可以得到一个虚拟节点树,使用这个虚拟节点树就可以渲染页面
VNode
VNode是vue中的一个类,通过new Vnode来生成虚拟节点,我们所谓的虚拟节点,本质上就是一个js对象,所谓的虚拟DOM树,就是由一个个js对象构成
在vue中,VNode的类型有以下几种
- 注释节点
- 文本节点
- 元素节点
- 组件节点
- 函数式组件
- 克隆节点
由于vue对组件采用了虚拟DOM来更新视图,当属性发生变化时,整个组件都要进行重新渲染的操作,但组件内并不是所有DOM节点都需要更新,所以将vnode缓存并将当前新生成的vnode和上一次缓存的oldVnode进行对比,只对需要更新的部分进行DOM操作可以提升很多性能
Patch
对两个虚拟节点进行比对是虚拟DOM中最核心的算法(即patch),它可以判断出哪些节点发生了变化,从而只对发生了变化的节点进行更新操作,具体关于patch的文章可以看我这篇博客
未动笔,未来可寄