vue的响应式原理----实现一个简易版本的vue

首先我们先回想一下vue的结构  自己在打印台里面打印一个vue实例  然后看一下整理结构

let vm = new Vue({
       el: '#app',
       data:{
        msg:'hello world',
        count:0,
        person:{
            name:'555',
            sex:'man'
        }

       },
   })

  接下来我们就自己实现插值表达式的解析和v-text v-model这些功能

  <div id="app">
     <h1>{{msg}}</h1>
     <h1>{{count}}</h1>
     <div v-text="msg"></div>
     <input type="text" v-model="msg"></input>
     <input type="text" v-model="count"></input>
    </div>

  我们看一下整体结构

我们需要一个vue类 Observer类 Dep类 Wacher类 Compiler类

我们看一下面这张关系图

vue的响应式原理----实现一个简易版本的vue

 

 

 

Vue:可以把data中的成员注入到vue实例,并且data中的成员转化成getter和setter

  1.   负责接收初始化得参数
  2. 负责把data中的属性注入到Vue实例,转换成getter和setter
  3. 负责调用observer监听data中得所有属性的变化
  4. 负责调用compiler解析指令/插值表达式

结构:这个类中包含$options $el $data  三个参数用于记录构造函数传过来的参数和一个方法proxyData 负责把data中的属性注入到Vue实例,转换成getter和setter

Vue.js

class Vue{
    constructor(options){
        // 1.通过属性保存选项的数据
        this.$options =options || {}
        this.$data =options.data || {}
        this.$el = typeof options.el === 'string' ? document.querySelector(options.el) :options.el
      
        //2.把data中的成员转换成getter和setter
        this._proxyData(this.$data)
        //3.调用observerd对象  监听对象
        new Observer(this.$data)
        //4.调用complier对象,解析指令和插值表达式
        new Complier(this)
    }
    _proxyData(data){
        //遍历data的属性注入到vue实例中
        Object.keys(data).forEach(key=>{
        
            Object.defineProperty(this,key,{
                enumerable:true,
                configurable:true,
                get(){
                    return data[key]
                },
                set(newValue){
                    if(newValue === data[key]){
                        return
                    }
                    data[key] =newValue
                }
            })
        })
    }
}

Observer:能够对对象的所有成员属性进行监听如果拿到的是最新的值则通知Dep

  1.  负责把data选项中得属性转换成响应式数据
  2. data中的某个属性也是对象,把该对象转换成响应式数据
  3. 数据变化发送通知

结构:只有两个方法walk和defineReactive(data,key,value)

 observer.js

class Observer{
    constructor(data){
        this.walk(data)
    }
    walk(data){
        //1.判断data中时候是对象
        if(!data || typeof data !== 'object'){
            return
        }
        //2.遍历data对象的所有属性
        Object.keys(data).forEach(key=>{
            this.defineReactive(data,key,data[key])
        })
    }
    defineReactive(obj,key,val){
        let that =this
        /**  
         * 负责收集依赖,并发送通知
        */
        let dep = new Dep()
        //如果是val是对象,将对象变成响应式得
        that.walk(val)
        Object.defineProperty(obj,key,{
            enumerable:true,
            configurable:true,
            get(){
                //收集依赖
                Dep.target && dep.addSub(Dep.target)
                return val
            },
            set(newValue){
                if(newValue === val){
                    return
                }
                val =newValue
                //当前属性变成对象的时候将其变成响应式对象
                that.walk(newValue)

                //发送通知
                dep.notify()
            }
        })
        
    }
}

  

Compiler:用于解析插值表达式和v-text v-model 把数据更新到视图中

  1. 负责编译模板,解析指令/插值表达式
  2. 负责页面首次渲染
  3. 当数据变化后重新渲染视图
class Complier{
    constructor(vm){
        this.el =vm.$el
        this.vm = vm
        console.log('vm',vm)
        this.complier(this.el)
    }
    //编译模板,处理文本节点和元素节点
    complier(el){
        // console.log(el.childNodes)
        let childNodes = el.childNodes
        Array.from(childNodes).forEach(node =>{
            //处理文本节点
            if(this.isTextNode(node)){
                this.complierText(node)
            }else if(this.isElementNode(node)){
                //处理元素节点
                this.complierElement(node)
            }
            //判断node节点,是否有子节点,如果有子节点,要递归调用complier
            if(node.childNodes && node.childNodes.length){
                this.complier(node)
            }
        })
    }
    //编译元素节点,处理指令
    complierElement(node){
        Array.from(node.attributes).forEach(attr=>{
            let attrName = attr.name
            if(this.isDirective(attrName)){
                //v-text => text
                attrName = attrName.substr(2)
                let key =attr.value
                this.update(node,key,attrName)
            }
        })
    }
    update(node,key,attrName){
        let updateFn = this[attrName + 'Updater']
        updateFn && updateFn.call(this, node,key,this.vm[key])

    }
    textUpdater(node,key,value){
        node.textContent =value
        new Watcher(this.vm,key,(newValue)=>{
            node.textContent =newValue
        })
    }
    modelUpdater(node,key,value){
        node.value =value
        new Watcher(this.vm,key,(newValue)=>{
            node.value =newValue
        })
        //双向绑定
        node.addEventListener('input',()=>{
            this.vm[key] =node.value
        })
    }



    //编译文本节点,处理差值表达式
    complierText(node){
        // console.dir(node)
        //{{变量名字}}
        let reg = /\{\{(.+?)\}\}/
        let value = node.textContent
        if(reg.test(value)){
            let key = RegExp.$1.trim()
            node.textContent =value.replace(reg,this.vm[key])
            //创建watcher对象
            new Watcher(this.vm,key,(newValue)=>{
                node.textContent =newValue
            })
        }

    }
    //判断元素属性是否是指令
    isDirective(attrName){
        return attrName.startsWith('v-')
    }
    //判断节点是否是文本节点
    isTextNode(node){
        return node.nodeType === 3
    }
    //判断节点是否是元素节点
    isElementNode(node){
        return node.nodeType == 1
    }
}

  

 

Dep:用于添加观察者,当数据更新的时候通知观察者

  1. 收集依赖,添加观察者
  2. 通知所有观察者
class Dep{
    constructor(){
        //存储所有的观察者
        this.subs =[]
    }
    //添加观察者
    addSub(sub){
      if(sub && sub.update){
          this.subs.push(sub)
      }   
    }
    //通知依赖
    notify(){
        this.subs.forEach(sub=>{
            sub.update()
        })
    }
}

  

Wacher:内部有update方法,当拿到最新的值得时候更新视图

  1. 当数据变化触发依赖,dep通知所有的watcher实例更新视图
  2. 自身实例化的时候往dep对象中添加自己
class Watcher{
    constructor(vm,key,cb){
        this.vm = vm
        //data中的属性名称
        this.key = key
        //回调函数负责记录更新视图
        this.cb = cb
        /**  
         * 把watcher对象记录到Dep类的静态属性target
        */
        Dep.target =this
        //触发get方法,在get方法中会调用addSub
        this.oldValue =vm[key]
        Dep.target =null

    }
    update(){
        let newValue = this.vm[this.key]
        if(this.oldValue === newValue){
            return
        }
        this.cb(newValue)
    }
}

  

 

上一篇:WPF中修改DataGrid单元格值并保存


下一篇:GUC-1 模拟CAS算法