虽然工作使用的是Vue框架,但是对于尤大团队开发的这玩意,老实说,除了平时用来写写业务代码外,对于他的内部运行机制及底层原理方面,脑子总是嗡嗡的。希望做一个懂源码的小白~

当我开始有这个想法的时候,我就去github上面下载了整个Vue源码项目,不怂就是莽。打开一看,尼玛这是什么玩意,我是谁?我在哪?我要干嘛?感觉受到了侵犯!明显这么硬刚是不现实的。然后我在网上翻箱倒柜找到了一本Vue揭秘以及重读Vue中文文档API,由此展开了这场秃头之旅~

MVVM的前世今生
先简单介绍一下MVVM吧,MVVM全称为Model-View-ViewModel,把MVVM分开来讲,主要通过以下三个核心组件组成,这个就相当于不太和谐的婆媳关系:Model—不管她是娘还是娘子,她是包含业务和验证逻辑的数据模型View—不管她是娘子还是娘,她是定义屏幕中View展示的结构,布局和外观ViewModel—是这块夹心饼干中的受气包(不要四处看,希望以后不是你!),主要是扮演负责连接View和Model的角色,保证视图和数据的一致性

对于MVVM的前世--MVC的出现鉴于早期数据结构没有那么复杂的时候,就是Controller负责将Model的数据用View显示出来,换句话说就是在Controller里面把Model的数据赋值给View。但是随着Model数据结构越来越复杂,也就需要对数据结构进行解析的工作,才能把数据给View展示,但是这工作交给Controller去处理吗?对于Controller的定义其实并没有数据结构解析的功能,而且全部交由Controller去处理的话,会把C变得很臃肿。那怎么办呢?所以就有了ViewModel的出现,做着数据解析的工作。但其实这其中Controller的作用还是存在的,只是随着VM的出现而逐渐淡化~
个人粗浅了解就是将其中View的状态和行为抽象化,让我们能够将UI和业务逻辑分开。这些工作大部分都是ViewModel帮我们实现了,让我们尽可能少的操作DOM,主要把精力放在业务逻辑和数据展示方面。由此可知,这个ViewModel受气包起着举脚轻重的作用,也是干着所谓的响应式原理的工作,而这其中的第一步就是数据双向绑定,这在Vue中鱿鱼丝在2.0版本中的实现就是用Object.defineProperty(),后面鱿鱼丝发现了这个方法的一些限制吧,在即将出来的Vue3.0中将数据绑定实现的问题交由Proxy来实现,但是这就意味着有兼容的问题(鱿鱼丝:你们看我干嘛,我有办法)!?
数据双向绑定
其实稍微用过一段时间Vue的搬砖工,都难免会好奇他到底是怎么样去实现数据双向绑定的需求的?通过了解之后,发现有几种解决方案可以解决这个需求:第一种 解决提需求的人
其实要想工作得开开心心的方法就是解决那些给你提各种奇奇怪怪的需求的人了,当然是我们可爱的产品啦。但是这年头靠拳头解决事情是不可以滴,分分钟不是住医院就是去局里吃免费的饭~但是对于产品小姐姐这种美丽的生物,当然要用我们帅气的脸庞去睡服她,但是像我这种长相出众的人,稍微对产品小姐姐温柔点都会被说成渣男,哎~ 或者约去爬山? 
第二种 Object.defineProperty()
Vue 2.0 实现的这种需求的方法是Object.defineProperty() 先上语法:Object.defineProperty(obj, prop, descriptor)
参数说明:
obj:必需。目标对象
prop:必需。需定义或修改的属性的名字
descriptor:必需。目标属性所拥有的特性
返回值:传入函数的对象。即第一个参数obj;在MDN中对该方法的描述是会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。也就是说能够对传进去的obj对象中定义新属性或者修改obj对象中的属性,主要是obj对象属性中的数据属性(configurable、enumerable、value、writable)和存储器属性(getter、setter),利用 Object.defineProperty() 对数据进行劫持,设置一个监听器 Observer,用来监听所有属性,如果属性上发上变化了,就需要告诉订阅者 Watcher 去更新数据,最后指令解析器 Compile 解析对应的指令,进而会执行对应的更新函数,从而更新视图,实现了双向绑定~
Object.defineProperty() 是怎样对数据进行劫持的呢?上代码//主要是利用obj对象属性的存储器属性
var obj = {};
var initValue = 'hello';
Object.defineProperty(obj,"newKey",{
get:function (){
//当获取值的时候触发的函数
return initValue;
},
set:function (value){
//当设置值的时候触发的函数,设置的新值通过参数value拿到
initValue = value;
}
});
//获取值
console.log( obj.newKey ); //hello
//设置值
obj.newKey = 'change value';
console.log( obj.newKey ); //change value从而当View访问或者设置属性的时候,Vue是采用数据劫持结合发布/订阅模式的方式,通过Object.defineProperty() 来劫持各个属性的setter,getter方法,在数据变动时发布消息给订阅者,触发相应的监听回调。
但是Object.defineProperty()方法有一些弊端,就是无法对后续Vue data属性声明编译后添加的属性进行监听,而要通过Vue提供的方法Vue.set()和 vm.$forceUpdate()等方法去确保这个新属性同样是响应式的,且触发视图更新。无法监听属性的添加和删除、数组索引和长度的变更,这些就是Object.defineProperty 的实现所存在的很多限制~
第三种 Proxyue
作为Vue3.0的一大特色,Proxy很好地解决了Object.defineProperty 的这些限制,先上语法:
let proxy = new Proxy(target, handler);target 是用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理), 参数 handler 也是一个对象,其属性是当执行一个操作时定义代理的行为的函数,也就是自定义的行为。Proxy 的基本用法就如同上面这样,不同的是 handler 对象的不同,handler 可以是空对象 {} 但是不能设置为null,则表示对 proxy 操作就是对目标对象 target 操作。Proxy 目前提供了 13 种可代理操作,我们在这里讲双向数据绑定的两种:
--handler.get(target,property,receiver)get方法类似于Object.defineProperty中的get方法,用于拦截对象的读取属性操作,但是所接收的参数不一样:target 是指目标对象,property 是被获取的属性名 , receiver 是 Proxy 或者继承 Proxy 的对象,一般情况下就是 Proxy 实例。上代码:
let proxy = new Proxy({},{
get : function (target,prop,receiver) {
console.log(`get ${prop}`);
return receiver;
}
})
console.log(proxy.a) // get a
// Proxy{}从代码可以看出,上述 proxy 对象的 a 属性是由 proxy 对象提供的,所以 receiver 指向 proxy 对象。
紧接着,我们在讲他的设置属性值方法:
--handler.set(target, property, value, receiver)用于拦截设置属性值的操作,参数于 get 方法相比,多了一个 value ,即要设置的属性值~话不多说,先上码:
let proxy = new Proxy({},{
set : function (target,prop,value) {
console.log('success')
target[prop] = value;
}
}
})
proxy.count = 10; // success其实乍一看,好像也和Object.defineProperty方法没啥区别,但是细心一点观看你会发现,Proxy的方法并不是像Object.defineProperty方法一样,针对每一个对象属性进行数据监听,而是对整个对象进行监听,从而实现了可以监听属性的监听和修改,并可以支持 Map、Set、WeakMap 和 WeakSet!
Proxy的代理方法远不止上面两种,他还有用于拦截 new 操作符的construct方法,有判断对象是否具有某个属性时调用的has方法等等,总之Proxy的方法简单且强大,但是要想在实际应用上用好他还需要下苦功夫~再加上前面所说的其本身的 Proxy 的兼容性方面的问题,希望能够有更好的处理吧。
总之
回到本文要讲的Vue的双向数据绑定原理,其实方法除了正经和不正经的方法外,其实还有angular的脏检查等,但是这些只是响应式原理中的一个小小部分,希望自己能够继续学习下去吧~尽快写出下一章玉女心经吧。小潮鸡加油
