Labs 导读
Vue.js是一款适用于构建用户界面的渐进式JavaScript框架。它由尤雨溪在2014年推出,并迅速成为最流行的前端框架之一。Vue.js的设计目标是通过简单、灵活的API,提供一种高效、可复用和响应式的方式来构建现代化的Web应用程序。自Vue发行以来,就受到了国内外爆发式的关注,如今已经成为了最流行的前端框架之一,并且其已经具有了庞大的生态系统。Vue框架采用了MVVM的设计模式,本文简单介绍Vue2中MVVM的实现。
Vue.js作为一款流行的前端框架,其整体框架的设计采用了前端框架中常用的MVVM设计模式,将视图与数据相互解耦,分离了关注点并支持双向数据绑定,使得页面的变化呈响应式,能够根据数据的变化而自动更新视图。而Vue对MVVM模型的实现离不开数据的双向绑定以及虚拟DOM的页面渲染机制。本文接下来将从Vue的MVVM模型、数据双向绑定的实现以及虚拟DOM机制分别展开介绍。
Part 01、 MVVM模型
MVVM即Model-View-ViewModel,是前端三大MV*(MVC、MVP、MVVM)模型之一,是 MVC 模型的改进版本。MVVM模型通过数据双向绑定的方式来解耦视图和模型,有助于从概念层面上将图形用户界面的开发与业务逻辑或后端逻辑的开发分离开来,从而使视图不依赖于任何特定的模型平台,其优势在于明确地分离了界面和业务逻辑,使代码更易于维护、重用和测试。MVVM指代了三个部分,具体如下:
1.Model(模型):模型代表着应用程序中的数据和业务逻辑。在Vue.js中,模型可以是应用程序的数据对象、服务端API返回的数据,或者其他需要进行处理和展示的数据。
2.View(视图):视图是用户界面的可见部分,即用户所看到和与之交互的界面元素。在Vue.js中,视图由模板(Template)来定义,模板是一种声明式的HTML扩展语法,在模板中可以使用Vue的指令、插值表达式等来动态渲染数据。
3.ViewModel(视图模型):视图模型是连接视图和模型的桥梁,负责处理视图和模型之间的数据交互和逻辑控制。在Vue.js中,视图模型由Vue实例来表示,它是一个JavaScript对象,包含了模板中所需的数据、方法和计算属性等。
在Vue中,上图Controller的功能便是通过数据的双向绑定来实现的,而数据引发视图的更新则是通过虚拟DOM来实现的。在运用MVVM设计模式的情况下,Vue的工作原理如下:
- 当用户与视图进行交互(例如点击按钮、输入表单等),视图触发相应的事件。
- 视图模型监听这些事件,并根据业务逻辑更新模型的数据。
- 当数据发生变化时,模型发送通知给视图模型,视图模型根据变化更新视图。
- 视图根据最新的数据重新渲染,展示给用户。
上述过程主要为数据更新时的执行逻辑,这个过程在Vue整个生命周期中属于更新阶段。而Vue 的生命周期实际上就是 Vue 组件从创建到销毁的整个过程,被分为了四个阶段,八个钩子函数(可以理解为事件回调函数)。分别为创建阶段、挂载阶段、更新阶段、销毁阶段,MVVM中视图与模型的初始化和数据双向绑定则在创建阶段的beforeCreate以及挂在阶段的mounted中实现。
Part 02、 数据双向绑定的实现
Vue数据双向绑定的实现实质上也就是MVVM中Controller的实现。Vue的数据双向绑定是通过数据劫持结合发布/订阅者的模式实现的,而数据劫持在Vue底层是通过Object.defineProperty()方法实现的。Object.defineProperty()方法是原生JS提供的一个定义对象属性的接口,通过该方法的get和set配置项可以实现在一个对象中代理另一个对象的属性变化,即getter和setter,这使得我们在访问或修改对象的属性时可以执行自定义的逻辑。
在Vue中,当我们定义一个响应式数据时,Vue会将该数据转化为一个称为“响应式代理对象”的对象(data配置项中的属性)。在这个代理对象中,Vue就会使用Object.defineProperty()方法为对象中所有属性定义一个getter和setter。从而当我们访问响应式代理对象的属性时,Vue的getter会被触发,从而执行一些与数据相关的逻辑,例如依赖追踪和通知订阅者。而当我们修改响应式代理对象的属性时,Vue的setter会被触发,它会接收新的值并执行一些对应的更新操作,例如触发视图重新渲染。这种方式称为“劫持”或“拦截”对象的访问和修改操作,以实现数据的响应式。
在Vue劫持了所有需要监听的所有属性后,结合Vue的消息发布-订阅模式,每当数据变化就会通过消息发布-订阅模式的方式监测到数据的变化,并重新编译视图以此实现响应式。Vue中数据双向绑定机制如图所示,其中Dep 是订阅收集器。
其中,Vue的数据监测也是通过数据劫持的方式实现的,但Vue只会对对象进行数据劫持为其生成setter和getter,而对于数组Vue并不会对其进行数据劫持。对于数组Vue的处理方式是监测数组数据调用的方法,当调用了能够改变原数组的方法时,Vue会将该方法进行二次封装。原本这些方法只做了他们原本该做的事情,但Vue包装过之后,还加了一件事进去,即重新解析页面生成虚拟DOM并执行diff算法生成新的DOM结构,交给浏览器进行页面的更新渲染,以此实现对数组数据改变的响应。但是值得注意的是,那些不会引起原数组改变的方法以及通过数组索引直接进行修改而没有调用任何方法的情况,Vue并不会监测到,因此也不会出现响应式的效果。
Part 03、 虚拟DOM机制
Vue的虚拟DOM是一种在内存中维护的轻量级DOM树,在页面初始渲染时生成,虚拟DOM机制的原理如下:
1)生成虚拟DOM:当Vue组件首次渲染时,会生成一个虚拟DOM树,称为初始虚拟 DOM。这棵树的结构与实际的DOM结构相似,但是只是在内存中存在,并没有直接渲染到页面上。
2)更新虚拟DOM:当组件的状态发生变化时,Vue 会生成一个新的虚拟DOM树,称为更新虚拟DOM。该树与初始虚拟DOM相比,只有发生变化的部分会被重新创建,其余部分则会被复用。
3)对比并计算差异:Vue会对比初始虚拟DOM和更新虚拟DOM,找出两棵树之间的差异。这个过程称为虚拟DOM的diff算法。通过这个算法,Vue可以高效地找出需要进行修改、添加或移除的节点。
4)批量更新真实DOM:根据差异的计算结果,Vue会将需要进行修改的部分同步到实际的DOM中。由于虚拟DOM是在内存中操作的,因此可以通过批量更新的方式,将多个DOM修改操作合并为一个,从而减少实际的DOM操作,提高性能。
在常规的直接操作实际DOM的方式下,页面数据的每次改变都会引发浏览器重新计算和渲染整个DOM树。这种操作会涉及到大量的DOM访问和修改,而这些操作通常是相对较慢的,尤其在涉及复杂的页面结构和大量的DOM元素时。在数据发生改变需要重新解析页面结构的时候,Vue并不是直接将解析的页面结构直接交给浏览器渲染,而是先根据虚拟DOM采用Diff算法计算出DOM中更新的部分,然后只交给浏览器更新的部分去渲染,从而减少浏览器对DOM的操作和页面的重排重构,提高浏览器解析页面的性能。所以本质上来说,虚拟DOM是利用了JS代码的运行速度来减负了浏览器更新DOM的消耗。
Part 04、 总结
在Vue中,MVVM的实现主要是通过数据双向绑定以及虚拟DOM实现的,在Vue中的具体实现则是通过Vue实例来完成的。在Vue实例的生命周期中,在创建阶段生成Vue实例完成ViewModel的初始化;在挂载阶段使用Object.defineProperty()方法来劫持和观察数据对象的属性访问和修改,完成数据与视图的数据双向绑定;在更新阶段应用虚拟DOM+Diff算法来高效地确定需要进行修改、添加或删除的节点并将更新的节点交给浏览器渲染,而不是直接操作实际的DOM,提高了渲染效率。需要注意的是,在Vue中,虚拟DOM主要用于优化渲染性能,而非作为实现MVVM的核心机制。数据的双向绑定才是MVVM模型的主要实现方式,虚拟DOM则是为了提高性能而引入的辅助手段。这样,通过Vue实例作为ViewModel,开发者能够更方便地管理和操作应用的界面和数据,实现了视图和数据模型之间的双向绑定,使代码更具可读性和可维护性。