MVVM框架设计分析(以vue.js为例 )
MVVM 框架设计
MVVM 框架已经成为前端圈的主流,同时也诞生了很多优秀的前端框架,有最早的Angular,以及后起之秀Vue、React等,为我们开发者提供了极大的便利。那么,什么样的框架设计才算是MVVM框架呢?
MVVM框架的前身
最早使用Java开发的程序员应该了解过,当时有一种JSP
动态脚本语言,是以JAVA
语言作为载体,由服务端直出HTML
网页的一种技术。下面是JSP
的编写格式:
<html>
<head><title>Hello World</title></head>
<body>
<!-- 语法规则: <% 代码片段 %> -->
<% out.println("Your IP address is "); %>
<br/>
<% out.println(request.getRemoteAddr()); %>
</body>
</html>
页面结果输出为: Your IP address is: xx.xx.xx.xx
可以看到这种脚本语言的一些特点:
- 高耦合 (视图界面和功能逻辑互相糅合)
- 复用性差
- SEO相对较好 (服务端直出)
什么是MVVM
MVVM是一种程序设计框架。拆分出来就是:M-V-VM
- M: Modal(数据模型层)
- V: View (视图界面层)
- VM: ViewModal(视图模型层)
下图是一个Vue框架的结构:
可以看出:
- 视图(
View
)通过事件绑定影响数据模型(Modal
)- 数据模型通过数据驱动渲染视图
- 视图层和数据模型层是相互独立的,通过视图模型进行关联
这种通过View
和Modal
相互影响的过程就是MVVM最大的特性(双向绑定)
数据驱动视图
作者认为,MVVM最大的特点就是它。无须操作DOM
,只需修改数据,即可影响视图。
因为在这个框架出现之前,前端一直都是通过直接操作DOM
,来影响视图的。这样做最大的弊端就是频繁操作DOM
,会导致处理繁琐、代码冗余,而且如果DOM
操作不当就会导致页面渲染性能下降。浏览器重绘和回流
比如实现场景:在input
中输入某个值,对应显示在一个div
中。我们就需要监听input
值的变化,然后写入到对应的div
中。
<script type="text/JavaScript">
function valChange(e){
document.querySelector('#app').innerHTML = e.target.value
}
</script>
<input type="text" id="a" oninput="valChange(event)" onporpertychange="valChange(event)" />
<div id="app"></div>
这种写法有一个最大的缺点就是视图层和我们的业务逻辑还是有糅合,如果需要最大化解耦,看看Vue
如何去实现的:
<template>
<div>
<!-- 视图模板 -->
<input type="text" v-model="userName">
<div>{{userName}}</div>
</div>
<script>
export default {
data:{
return {
userName: '' // 数据模型
}
}
}
</script>
</template>
可以看到,当v-modal
绑定的值发生改变时,页面会自动渲染更新对应的值。我们只需要声明视图模板,并且绑定对应的数据模型,那么我们的视图层就再也无需过多关心,这极大提升前端开发的工作效率。
MVVM 的特点
1. 数据绑定
M-V-VM
框架核心就是数据绑定,将View
和Modal
进行关联。数据绑定实现的关键就是指令,指令的职责是当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。因此,操作DOM
相关的逻辑也是封装在指令中实现。参考 Vue.js 指令
Vue.js
提供了指令机制: 用户可以通过具有特殊前缀的HTML 属性(v-html
、v-text
等)来实现数据绑定,也可以使用常见的花括号({{}}
)模板插值,或是在表单元素上使用双向绑定(v-model
)。
Vue
会给动态更新的DOM
节点创建对应的指令对象,当观测到指令对象的值发生变化时,它所绑定的目标节点就会执行对应的DOM
操作。
2. 数据观测(监听)
数据观测(监听)的实现方式不同框架有不同的实现方式:一种是像Ng
提出的脏检查(dirty checking) 机制,而Vue
采用的是基于依赖收集的观测机制。此处重点讲解Vue
的实现原理:
-
数据劫持,将原生js对象改造成可观测对象
实现手段有两种: ES5的Object.defineProperty
方法以及ES6的Proxy
方式(数据劫持实现原理) -
注册订阅者 当
watcher
求值过程中,每一个被取值的数据对象,都会将当前作用域的watcher
作为自己的订阅者(写入到setter中),并成为当前watcher的一个依赖 -
触发更新 当被依赖的数据对象进行赋值操作时,它会通知自己的
watcher
重新求值,并触发更新
概念
-
watcher
:用来观测数据的对象,叫做watcher - 依赖收集: 通过重写getter、setter,将数据的依赖关系明确化,这样可以加快数据观察和更新的效率。
-
订阅者(dep)
:vue
中Dep
对象就是一种发布订阅者模式,订阅就是将watcher
push
到数组,发布就是将数组中的函数执行并pop
。vue/dep.js实现源码
3.虚拟DOM
虚拟DOM就是为了解决浏览器性能问题(频繁操作DOM)而被设计出来的。页面性能大部分的性能瓶颈都在操作DOM
上,而JS
引起页面卡顿的很少有,如果有10个div
节点依次插入到body
中,每插入一个div
都会导致页面重绘,可以想象如果有1000条数据同时加载,这样会严重影响用户体验。因此,Virtual DOM
就是为了解决这个问题被设计出来的。
虚拟的DOM的核心思想是:对复杂的文档DOM结构,提供一种方便的实现,进行最小化地DOM操作。
4.组件
Vue.js的组件可以理解为预先定义好了行为的ViewModel类,例如vue.js
比较流行的组件库(iVew)。
组件为开发者提供了可复用、可扩展的特征。vuejs 组件
组件的核心
- 模板(
template
) - 数据(
data
) - 外部参数(
props
) - 方法(
methods
) - 钩子函数(
hook function
)
后记
本文只是简单的介绍了MVVM的设计思想,以及社区实现的项目。每个具体框架实现的细节这里也没有过多的展开,比如diff
算法实现原理、模板编译相关,相关源码分析等。后续将会逐一展开进行讨论。