浅谈vue响应式原理及发布订阅模式和观察者模式

一.Vue响应式原理

首先要了解几个概念:

数据响应式:数据模型仅仅是普通的Javascript对象,而我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率。

双向绑定:数据改变,视图改变,数据也随之改变,我们可以使用v-model在表单上创建双向数据绑定。

数据驱动是Vue最独特的特性之一:开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图。

vue2.X中的响应式原理是基于defineProperty,兼容IE8以上版本,核心原理代码如下:

              let data={
         msg:'hello',
         count:10
          }
        let vm={}
        proxyData(data)
        function proxyData(data){
            Object.keys(data).forEach(key=>{
                Object.defineProperty(vm,key,{
                    enumerable:true,
                    configurable:true,
                    writeable:true,
                    //获取值的时候执行
                    get(){
                        console.log('get:',key,data[key])
                        return data[key]
                    },
                    //设置值的时候执行
                    set(newValue){
                        data[key]=newValue
                        console.log('set:',key,newValue)
                        document.querySelector('#app').textContent=data[key]
                    }
                })
            })
        }
         vm.msg //获取(get方法) hello
         vm.msg='hello World' //设置新属性值并渲染到页面(set方法)
         vm.msg //hello World  

vue3.X中的响应式原理是基于Proxy,直接监听对象,而非属性,ES6中新增,IE不支持,性能由浏览器优化,性能比defineProperty要好,代码的话相比较defineProperty要简洁一些,对于多个属性的值不需要进行循环遍历处理。

                let data={
            msg:'hello',
            count:0
        }
        
        //模拟 Vue 实例
        let vm=new Proxy(data,{
            //执行代理行为的函数
            //当访问 vm 的成员会执行
            get(target,key){
                console.log('get,key:',key,target[key])
                return target[key]
            },
            set(target,key,newValue){
                console.log('set,key:',key,newValue)
                if(target[key] === newValue){
                    return 
                }
                target[key]=newValue
                document.querySelector("#app").textContent=target[key]
            }
        })
        //测试
        vm.msg='Hello World'
        console.log(vm.msg)

二.发布订阅模式和观察者模式

1.发布/订阅模式
这个概念有些抽象,下面举个例子说明下,家长比较关心孩子成绩,天天问孩子成绩出来没,假设可以到孩子所在班级去订阅孩子成绩,一旦考试成绩出来,相当于触发了一个事件,最后有班级的老师以短信的形式通知给家长,

不需要天天问孩子成绩出来没,家长就是事件的订阅者,老师是事件的发布者,孩子所在的班级可以假想成一个事件的中心。vue中的自定义事件都是基于发布/订阅模式的。下面模拟下发布订阅模式的运行机制:

//事件触发器
        class EventEmitter(){
            constructor(){
                // 初始化对象{ 'click':[fn1,fn2],'change':[fn] }
                this.subs=Object.create(null)
            }
            //注册事件
            $on(eventType,handler){
                this.subs[eventType] = this.subs[eventType] || []
                this.subs[eventType].push(handler)
            }
            //触发事件
            $emit(eventType){
                if(this.subs[eventType]){
                    this.subs[eventType].forEach(handler => {
                        handler()
                    })
                }
            }
        }
        //测试
        
        let em =new EventEmitter()
        
        em.$on('click',()=>{
            console.log('click1')
        })
        em.$on('click',()=>{
            console.log('click2')
        })
        em.$emit('click') //打印结果 click1,click2

二.观察者模式

观察者模式和订阅模式的区别是没有事件中心,只有发布者和订阅者,并且发布者需要知道订阅者的存在.

概念:

观察者 –Watcher

update():当事件发生时,具体要做的事情。

发布者 –Dep

subs数组:存储所有的观察者

addSub():添加观察者

notify():当事件发生,调用所有观察者的update()方法

//发布者-目标
        class Dep{
            constructor() {
                //记录所有的订阅者
                this.subs=[]
            }
            //添加订阅者
            addsub(sub){
                if(sub && sub.update){
                    this.subs.push(sub)
                }
            }
            //发布通知
            notify(){
                this.subs.forEach(sub=>{
                    sub.update()
                })
            }
        }
        //订阅者-观察者
        class Watcher{
            update(){
                console.log('update')
            }
        }
        //测试
        let dep=new Dep()
        let watcher=new Watcher()
        dep.addsub(watcher)
        dep.notify() //打印结果 update

总结:

观察者模式是由具体目标调度,比如当事件触发,Dep就会去调用观察者的方法,所以观察者的订阅者和发布者之间是存在依赖的。

发布订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。