mini-vue响应式(3-1-2)

前言:最近关注了vue,想对比一下和react的实现的区别,方便以后项目做选型。

打算了解对比一下基本实现,才能写出更符合 框架设计思想高效率的代码,出了问题才能更好的排错。

vue响应式模拟

准备工作

  • 数据驱动
  • 响应式核心
  • 发布订阅和观察者
数据驱动
  • 数据响应式:数据模型仅仅是普通的js对象,而当我们修改数据时,视图会进行对应的更新,避免了繁琐的dom操作,提高开发效率
  • 双向绑定:数据改变,视图改变;视图改变,数据也随之改变,使用v-model在表单元素上创建双向数据绑定
  • 数据驱动是vue最独特特性之一:开发过程仅需要关注数据本身,不需要关心数据是如何渲染到视图的
数据响应式

vue2是用的Object.defineProperty来进行对对象的get,set进行劫持来实现的双向绑定类似:

let data ={msg:'hello'}
let vm={}
Object.defineProperty(vm,'msg',{
    enumerable:true,
    configurable:true,
    get(){
        return data.msg
    }
    set(newValue){
        if(newValue === data.msg){
            return 
        }
        data.msg=newValue
        document.querySelector('#app').textContent=data.msg
    }
})

vm.msg='HelloWorld'
console.log(vm.msg)

vue3使用的是代理对象,直接监听对象,不监听属性,性能由浏览器优化,

let data={msg:'hello',count:0}
let vm = new Proxy(data,{
    get(target,key){
        return target[key]
    }
    set(target,key,newValue){
        if(target[key] === newValue){
            return 
        }
        target[key] = newValue
        document.querySelector('#app').textContent=target[key]
    }
})

贴个图来展示下俩的区别
image.png

  • Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
  • Object.defineProperty不能监听数组。是通过重写数据的那7个可以改变数据的方法来对数组进行监听的。
  • Object.defineProperty 也不能对 es6 新产生的 Map,Set 这些数据结构做出监听。

Proxy不会直接侵入对象去做劫持,而是直接对对象整体进行包装一层。

不过我们目前实现的简易版vue是基于vue2去做的。

发布/订阅模式和观察者模式

发布/订阅模式

  • 订阅者
  • 发布者
  • 信号中心

说明:我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern)
vue中的自定义事件就是一个发布订阅模式$emit,$on.

//eventBus.js
//事件中心
let eventHub=newVue()
//ComponentA.vue
//发布者
addTodo:function(){//发布消息(事件)
    eventHub.$emit('add-todo',{text:this.newTodoText})
    this.newTodoText=''
 }
//ComponentB.vue//订阅者
created:function(){//订阅消息(事件)
    eventHub.$on('add-todo',this.addTodo)
}

我们模拟一个vue的发布订阅,如下

class EventEmitter{
    constructor(){
        this.subs={}
    }
    $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()
            })
        }
    }
}

var bus= new EventEmitter()
//注册事件
bus.$on('click',function(){
    console.log('click')
})
bus.$on('click',function(){
    console.log('click1')
})
//触发事件
bus.$emit('click')
观察者模式
  • 观察者watcher:update当事件发生时更新
  • 目标(发布者)-Dep

    • subs数组:储存所有观察者
    • addsub添加观察者
    • notify事件发生时通知观察者
  • 没有事件中心
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('更新六')
    }
}
let dep= new Dep()
let watcher = new Watcher()
dep.addSub(watcher)
dep.notify()
总结

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

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

从使用层面上讲:

  • 观察者模式,多用于单个应用内部
  • 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件
    image.png

然后我们分析一下vue中的实例化及更新流程,如图
image.png

  • vue:把传入的data注入vue实例,并且转换为set/get
  • Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知 Dep,Dep再调用观察者更新
  • Compiler:解析每个元素中的指令/插值表达式,并替换成相应的数据
  • Dep:添加观察者(watcher),当数据变化通知所有观察者
  • Watcher:数据变化更新视图

你可能感兴趣的:(vue.js,源码分析)