目的
为了真正的了解MVVM的核心思想,本文以Vue框架为切入点,手写模拟实现Vue框架的双向数据绑定,本文适合想进一步了解Vue底层的同学,当然对于想彻底搞懂MVVM原理的同学也会有所启发。
MVVM概念
MVVM的概念可以参考很多文章,比如这篇,其最核心的就是实现视图和视图模型的双向绑定。本文主要探究MVVM在Vue中的实现,下面将会围绕这个主题进行展开。
Object.defineProperty()
在讲解Vue的双向绑定之前,先来回顾一下Object.defineProperty(),在MDN中也有详细的解释,已经知道的同学可以跳过这个章,没看过的同学建议往下看。
1 | var person = {}; |
上述代码在我们的日常开发中司空见惯,打印结果如下:
现在我们改写一下
1 | var person = {}; |
在IE8以上浏览器中打印结果如下:
一点问题没有。下面我们改一下这个对象属性,
1 | var person = {}; |
结果如下:
我们发现结果没有变化,直接使用”对象.属性”的方法已经失效,查一下API,在代码中添加writable属性
1 | var person = {}; |
结果如下:
现在已经可以修改对象属性了,所以writable属性就是可复写的。在API中还存在configurable和enumerable属性,MDN这么说的:
configurable
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。
enumerable
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
如果觉得不明白,看下面这段代码:
1 | var person = {}; |
结果如下:
简而言之:configurable 就是可删除的,enumerable就是可遍历的。在API中还有两个配置项set和get,废话不说,直接上代码:
1 | var person = {}; |
结果报错
上述错误的意思是我们要把writable和value配置项去掉。为什么?get和set方法是获取和设置值的函数,有了它们就不需要writable和value配置项了。如下:
1 | var person = {}; |
结果如下:
在设置和获取这个对象的属性时,都会打印相应的信息。一旦对象的信息有所修改,我们都能第一时间发现它,这就是\数据劫持**。当对象的值改变的时候,如果我们能够自动去更新对应的视图,就能够完成从\对象视图模型(VM)=>视图**的单向数据流绑定。
模拟Vue的数据劫持
在讲解数据劫持之前,先来看下面一段代码
1 | <div id="app"> |
1 | let vue = new Vue({ |
这是一个最最基本的Vue程序,但是我们没有导入Vue的库,现在我们来看界面的效果:
现在我们没有看到任何效果,控制台还输出错误。好,我们现在只是搭建了一个空壳,下面我们一步步来实现和完善它。
劫持Vue中的data属性中的对象
我们先来定义一个Vue类,然后劫持传入配置项的data属性中的对象,代码如下:
1 | function Vue(options={}){ |
使用$options用于保存Vue的配置参数对象,_data用于存储真正的数据。我们打印下述信息:
1 | console.log(vue); |
看一下效果:
我们需要的效果实现了!我们把代码整理一下:
1 | let vue = new Vue({ |
我们把数据劫持的代码逻辑写在Observe类中,我们下面就是围绕这个展开。
呃…现在我们的效果真达到了么?我们重新设置一下vue对象
1 | let vue = new Vue({ |
结果显示如下
我们发现name属性还是有set和get方法,但是degree对象还嵌套着对象,所以这个时候我们需要递归遍历整个对象。于是添加修改代码如下:
1 | function Observe(data) { |
主要修改两个地方,在var val = data[key]后添加observe(val),对获取的属性值进行观察,然后在设置递归终止条件, if(typeof data!=”object”) return;最后效果如下:
下面再来写一段代码:
1 | let vue = new Vue({ |
结果如下:
我们发现name属性其实已经被劫持到了,但是传进去的对象没有被劫持到,所以在set方法里面也需要添加observe函数。如下所示:
1 | function Observe(data) { |
结果如下:
上述基本完成了对于配置对象中data属性的数据劫持。
数据代理
## References