尝试实现vue中的MVVM--菜鸡的小日子

引言

一直想要好好学一下关于框架原理的东西,奈何以前的课程任务太重,现在终于得空可以学习学习,跟着一个牛逼的博客做下去,相信自己能够得到很大的提升的哈哈哈哈哈哈,不想看我的可以直接去他那里seesee,传送门

前期知识

先了解一下MVC和MVVM地工作原理:
MVC可谓是十分经典:
在这里插入图片描述

  • 视图(View):用户界面。
  • 控制器(Controller):业务逻辑
  • 模型(Model):数据保存
    各部分之间的通信方式如下:
  • View 传送指令到 Controller
  • Controller 完成业务逻辑后,要求 Model 改变状态
  • Model 将新的数据发送到 View,用户得到反馈
  • 所有通信都是单向的。

MVVM现今也是十分的流行:
在这里插入图片描述

  1. 所有的通信都是双向的
  2. view和model层不发生关系,所有的都通过ViewModel传递~~
  3. view层其实比较薄,而ViewModel比较厚,所有的逻辑代码什么的都在这里面
  4. view和ViewModel采用的是双向绑定,view的变动自动反映在viewModel中,反之亦然~

先尝试一个小demo

在vue中其实比较核心的是Object.defineProperty(obj, prop, descriptor)这个API。
我们需要注意的是,使用的时候不能同时设置访问器(getter和setter)和writeable或者value,否则会会报这样的错误:
TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

step1:defineProperty

let callback={
    target: null
}

let defineReactive=function(object,key,value){
    let array=[]
    Object.defineProperty(object,key,{
        configurable: true,
        enumerable:true,
        get: function(){
            if(callback.target){
                array.push(callback.target)
            }
            return value
        },
        set: function(newValue){
            if(newValue!=value){
                array.forEach(fun => {
                    fun(newValue,value)
                });
            }   
            value=newValue;
        }
    })
}


let object={};
defineReactive(object,'test','test');
callback.target=function(newValue,oldValue){
    console.log("我被添加进去了,新的值是",newValue);
}
object.test;
callback.target=null;
object.test='test2';
callback.target=function(newValue,oldValue){
    console.log("添加第二个函数,新的value",newValue);
}
object.test
callback.target=null;
object.test='test3';

当我们取值的时候,函数自动帮我们添加针对当前值的依赖,当我们的值发生变化的时候,会自动处理这些依赖,比如我们的dom变化的时候。
这其实也是vue的核心代码,只是它的实现要远比我们的复杂许多!

step2 Dep

上一步我们实现了VUE的依赖收集和触发,但是我们只是将它放在了一个内置的数组里面,虽然容易理解但是十分不好维护,为了更加好地维护,需要事先一个维护依赖的类!
类下属性:target 函数,用于存放需要添加的依赖
实例下属性
subs/array:存放依赖
addsub/function: 添加依赖
removesub/function:删除依赖
notify/function:执行依赖

let Dep=function(){
    //实例属性
    this.subs=[]
    //实例方法
    this.addSub=function(sub){
        this.subs.push(sub);
    }
    this.removeSub=function(sub){
        const index=this.subs.indexOf(sub);
        if(index>-1){
            this.sub.splice(index,1);
        }
    }
    this.notify=function(newValue,oldValue){
        this.subs.forEach(func => {
            func(newValue,oldValue)
        });
    }
}
//类属性
Dep.target=null;

let defineReactive=function(object,key,value){
    let dep=new Dep();
    Object.defineProperty(object,key,{
        configurable:true,
        enumerable:true,
        get: function(){
            if(Dep.target){
                dep.addSub(Dep.target)
            }
            return value
        },
        set:function(newValue){
            if(newValue!=value){
                dep.notify(newValue,value);
            }
            value=newValue;
        }
    })
}

let object={};
defineReactive(object,'test','test');
Dep.target=function(newValue,oldValue){
    console.log('我被添加进去了,新的值',newValue);
}
object.test;
Dep.target=null;
object.test='test2';
Dep.target=function(newValue,oldValue){
    console.log('添加第二个函数,新的值是',newValue);
}
object.test;
Dep.target=null
object.test='test3';
  1. dep类将监听属性和处理依赖进行了一定的解耦,但是解耦不是很完全,在我们触发依赖的时候还是得传递新旧值。
  2. 删除依赖肯定在外部环境中,但是我们的dep实例是在defineReactive作用域里面,外部没有办法直接进行调用!
    vue中的Watcher类处理了以上两个问题,后面说!
    刚刚的Dep类可以在es6的语法下:
class Dep{
     constructor(){
         this.subs=[];
     }
     addSub(sub){
         this.subs.push(sub);
     }
     removeSub(sub){
         const index=this.subs.indexOf(sub);
         if(index>-1){
             this.subs.splice(index,1);
         }
     }
     notify(){
         this.subs.forEach(func=>func(newValue,oldValue))
     }
 }
//类属性
Dep.target=null;

step3 Watcher

  1. 解耦不完全,需要传递参数
  2. 没有地方可以移除依赖
//其中wacher是存入subs数组里面的一个个元素
let Watcher= function(object,key,callback){
    this.obj=object;
    this.getter=key;
    this.cb=callback;
    this.dep=null;
    this.value=undefined;

    this.get=function(){
        Dep.target=this;
        let value=this.obj[this.getter];//有个object、get操作
        Dep.target=null;
        return value;
    }

    this.update=function(){
        const value = this.obj[this.getter];
        const oldValue=this.value;
        this.value=value;
        this.cb.call(this.obj,value,oldValue);
    }
    this.addDep=function(dep){
        this.dep=dep;
    }
    this.value=this.get();
}


let Dep=function(){
    this.subs=[];
    this.addSub=function(sub){
        this.subs.push(sub);
    }
    this.removeSub=function(sub){
        const index=this.subs.indexOf(sub);
        if(index>-1){
            this.subs.splice(index,1)
        }
    }
    this.notify=function(){
        this.subs.forEach(watcher => {
            watcher.update();
        });
    }
}

Dep.target=null;

let defineReactive=function(object,key,value){
    let dep=new Dep();
    Object.defineProperty(object,key,{
        configurable:true,
        enumerable:true,
        get:function(){
            if(Dep.target){
                dep.addSub(Dep.target)
                // 添加 watcher 对 dep 的引用
                Dep.target.addDep(dep)
            }
            return value;
        },
        set: function(newValue){
            if(newValue!=value){
                value=newValue;
                dep.notify();
            }
        }
    })
}

let object = {}
defineReactive(object, 'test', 'test') 

let watcher = new Watcher(object, 'test', function(newValue, oldValue){
    console.log('作为 watcher 添加的第一个函数,很自豪。新值:' + newValue)
})
object.test = 'test2'
// 作为 watcher 添加的第一个函数,很自豪。新值:test2

let watcher2 = new Watcher(object, 'test', function(newValue, oldValue){
    console.log('作为 watcher 添加的第二个函数,也很自豪。新值:' + newValue)
})
object.test = 'test3'
// 作为 watcher 添加的第一个函数,很自豪。新值:test3
// 作为 watcher 添加的第二个函数,也很自豪。新值:test3

// 接着我们来试一下删除依赖,把 watcher2 给删除
watcher2.dep.removeSub(watcher2)
object.test = 'test4'
// 作为 watcher 添加的第一个函数,很自豪。新值:test4

上面的代码实现了一个Watcher,实例里面保存了:需要监听的对象(object)、取值方法(key)、对应的回调(callback)、需要监听的值(value)、以及取值函数(get)和出触发函数(update),这样我们就把依赖相关的所有内容都保存在了这个实例里面。
为了保存对Dep的引用,在Watcher中设置了dep,用于保存这个监听被那个Dep引用了。
编写代码的时候我们不用特地的触发get来添加依赖因为我们将之前的get值的代码放在了watcher里面:
修改前:

Dep.target = function(newValue, oldValue){console.log('我被添加进去了,新的值是:' + newValue)}  
object.test
Dep.target = null  

修改后:

this.get = function(){
    Dep.target = this
    let vaule = this.obj[this.getter]
    Dep.target = null
    return value
}

this.value = this.get()

最终,通过以上的代码我们解决了之前的问题。
问题:这时候呢我们又出现了一个问题:那就当前的Watcher还是需要优化的,比如如果被多个Dep引用的话,就得存一个数组嘤嘤嘤

setp4 优化Watcher

目前为止我们做了三个东西

  1. 通过defineReactive函数,实现了对数据取值和设置的监听!!Object.defineProperty
  2. 通过Dep类,实现了依赖的管理
  3. 通过Watcher类,实现了解耦等等。

问题:

  1. 一个watcher对应多个属性。
  2. 多个Dep依赖于同一个Watcher的情况。
//其中wacher是存入subs数组里面的一个个元素
let Watcher= function(object,getter,callback){
    this.obj=object;
    this.getter=getter;
    this.cb=callback;
    this.deps=[];
    this.value=undefined;

    this.get=function(){
        Dep.target=this;
        let value=this.getter.call(object)//有个object、get操作
        Dep.target=null;
        return value;
    }

    this.update=function(){
        const value=this.getter.call(object);
        const oldValue=this.value;
        this.value=value;
        this.cb.call(this.obj,value,oldValue);
    }
    this.addDep=function(dep){
        this.deps.push(dep);
    }
    //新调节的取消依赖的方法
    this.teardown=function(){
        let i=this.deps.length;
        while(i--){
            this.deps[i].removeSub(this)
        }
        this.deps=[];
    }
    this.value=this.get();
}


let Dep=function(){
    this.subs=[];
    this.addSub=function(sub){
        this.subs.push(sub);
    }
    this.removeSub=function(sub){
        const index=this.subs.indexOf(sub);
        if(index>-1){
            this.subs.splice(index,1)
        }
    }
    this.notify=function(){
        this.subs.forEach(watcher => {
            watcher.update();
        });
    }
}

Dep.target=null;

let defineReactive=function(object,key,value){
    let dep=new Dep();
    Object.defineProperty(object,key,{
        configurable:true,
        enumerable:true,
        get:function(){
            if(Dep.target){
                dep.addSub(Dep.target)
                // 添加 watcher 对 dep 的引用
                Dep.target.addDep(dep)
            }
            return value;
        },
        set: function(newValue){
            if(newValue!=value){
                value=newValue;
                dep.notify();
            }
        }
    })
}

let object={};
defineReactive(object,'num1',2);
defineReactive(object,'num2',4);
let watcher=new Watcher(object,function(){
    return this.num1+this.num2
},function(newValue,oldValue){
    console.log(`这是一个监听函数,${object.num1} + ${object.num2} = ${newValue}`)
});
let watcher2=new Watcher(object,function(){
    return  this.num1*this.num2
},function(newValue,oldValue){
    console.log(`这是一个监听函数,${object.num1} * ${object.num2} = ${newValue}`);
});
object.num1=4;
object.num2=11;

//测试取消
watcher2.teardown();
object.num1=5;
object.num2=12;

其实最终优化就是两点:

  1. 传入Watcher的不再是个key值,而是一个函数,这个函数里面呢,我们触发了多个属性的get
  2. 需要保存多个dep的话,将它用一个数组保存起来就可以了。
  3. 当然,为了方便取消这个watcher,我们需要的是添加函数为了取消所有dep对watcher的依赖。

自己小结一下
可能有点乱,之前我甚至没有把watcher和dep之间的关系弄懂,于是我花了个小图图,其实就是要实现一个一对多的关系,但是同时为了watcher能够保存对dep的引用,我们需要将依赖于此的dep保存到watcher里面(作用域啥的知识),以便于 引用。
在这里插入图片描述

step5 Observe

我们已经有了:

  1. defineReactive控制了对象属性,变为可监听结构
  2. Dep收集管理依赖
  3. Watcher一个抽象的依赖

defineReactiveDep改造了对象下面的某一个属性,将一个目标变成了观察者模式中的目标,目标发生变化的时候会调度观察者;
Watcher就是一个观察者,会注册到目标里面。
需要优化的地方:
4. 如果想要对象的属性都得以响应,必须对对象下的所有属性进行遍历,依次调用 defineReactive不是很方便。
5. 代码都在一个文件里面十分不便与管理
最后实现看 https://github.com/CoCoManYY/MyWheel/tree/master/myMVVM/demo/step5
其中的配置采坑笔记

step6 Array

之前的几个step实现了对象属性的监听,但是有关于数组的行为一直没有进行处理。

  • 调用方法:arr.splice(1,2,"something","something2")
  • 直接赋值:arr[1]='something'
    总结对原数组造成影响的方法:push、pop、shift、unshift、splice、sort、reverse。数组长度发生变化,数组内元素顺序发生变化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章