**
MVVM
**
Vue的双向数据绑定是指model(模型,也就是vue实例中的数据)和view(视图)的双向绑定,即一个发生改变,另一个也会改变。
首先了解一下什么是MVVM(model view viewmodel),在 MVVM 架构中,引入了 ViewModel 的概念,viewmodel相当于一个中间件。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。
以 Vue 框架来举例,ViewModel 就是组件的实例。View 就是模板,Model 的话在引入 Vuex 的情况下是完全可以和组件分离的。
整个变化到渲染的过程,大致就是:
首先通过数据劫持来监听data的数据变化,一旦监听到了数据发生变化,订阅发布开始获取数据,然后调用Dep的notify方法发布通知到每个订阅,订阅根据最新的数据渲染Dom。
- 三大核心
实现双向数据绑定主要是依据以下三大核心:
(1)observer 监听器
(2)watcher 订阅者
(3)compile 解析器
先创建一个vue实例:
const vm=new Vue({
data() {
return {
msg: '111111',
name: 'qwert',
a: {
b: 'bbbbb'
}
}
},
methods: {
setName() {
this.msg = '222';
}
},
created() {
this.msg = '212121';
console.log('实例初始化完成')
},
mounted() {
console.log('DOM挂载完成')
}
}).$mount('#app');
**
observer监听器
**
observer中有一个非常重要的函数Object.defineProperty(obj,prop,descriptor),它有三个参数:
• obj:要在其上定义属性的对象。
• prop:要定义或修改的属性的名称。
• descriptor:将被定义或修改的属性描述符
该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
我们通过Object.defineProperty( )设置了对data属性劫持,对其get和set进行重写操作,get就是我们在读取这个属性值时触发一个函数,我们一般会将watcher添加过程放到这个函数,set就是在设置data属性这个值触发函数。
组件挂载时,会遍历data中所有的属性,然后给每个属性利用Object.defineProperty()函数进行劫持,并对每个属性对应一个new Dep(),Dep是专门收集依赖、删除依赖、向依赖发送消息的,也就是对订阅者watcher做管理,毕竟一个data可以在多个地方使用,当数据改变那对应的不同的watcher都需要修改。
class Observer{
constructor(data){
this.$data = data;
this.observer(this.$data);
}
observer(obj){
// 如果data不是一个对象的话,提示错误,
// 因为只有对象才能调用Object.defineProperty
if(typeof obj !== 'object') return;
Object.keys(obj).forEach(key =>{
this.defineReactive(obj, key, obj[key]);
//遍历对象的每一个属性都调用defineReactive方法进行劫持
})
}
defineReactive(obj, key, value){
// 判断如果当前的值还是对象的话,递归劫持
if(typeof value === 'object') this.observer(value);
let dep = new Dep();//新建一个订阅器,管理订阅者watcher们
Object.defineProperty(obj, key, {
get(){
if(window.target){
//当前Dep.target是指的Watcher(订阅者)实例,是来自于下面的Watcher类里的,
//这里又将watcher放入订阅器dep中进行管理,实现了将监听这个数据变化的订阅者放到订阅者数组中
dep.addSubs(); // 向dep实例中添加Watcher实例
}
return value;
},
set: (newVal) =>{
if(value === newVal) return;
// 防止 newVal为对象的情况,需要重新将对象中的属性变为响应式
this.observer(