zoukankan      html  css  js  c++  java
  • Vue响应式原理底层代码模拟实现

    整体分析Vue的基本结构如下图所示:(备注:完整代码github地址https://github.com/1512955040/MiniVue)


    上图中,为我们模拟最小vue的整体结构,首先创建一个vue类型,它负责把data中的成员注入到vue实例中,并且转化成getter/setter,observer的作用是数据劫持,对data中的属性进行数据监听,如果数据发生变化会获取到最新的值,并通知dep。Compiler的作用是解析每个元素中的指令和差值表达式并替换成相应的数据。Dep的作用是添加观察者,当数据发生变化时通知所有的观察者。Watcher内部有一个Update方法负责更新视图,下面我们用代码的方式一一进行实现。

    1.Vue.js功能:

    1-1负责接收初始化的参数(选项)

    1-2负责把data中的属性注入到vue实例,转化成getter/setter

    1-3负责调用observer监听data中所有属性的变化

    1-4负责调用Compiler解析指令/差值表达式

    类图结构如下:

    如上图所示:vue类中有三个属性,分别是$options,$el,$data,这三个属性记录构造函数中传过来的参数。_proxyData为vue类中的方法

    所以以_开头的成员就是私有成员,这个方法的功能是把data中的属性转化为getter和setter注入到vue实例中。

    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,注入到vue实例中
            this._proxyData(this.$data)
            //3.调用observer对象,监听数据的变化
            new Observer(this.$data)
            //4.调用compiler对象,解析指令和差值表达式
            new Compiler(this)
        }
        //把Vue的属性转化为getter和setter,注入到Vue实例中
        _proxyData(data){
            //遍历data中所有属性
            Object.keys(data).forEach(key=>{
              //把data的属性注入到vue实例全局中
              Object.defineProperty(this,key,{
                  enumerable:true,
                  configurable:true,
                  get(){
                    return data[key]
                  },
                  set(newValue){
                    if(newValue===data[key]){
                        return 
                    }
                    data[key]=newValue
                 }
            })    
          })
        }
    }

    2.Observer.js功能(数据劫持):

    2-1 负责把data选项中的属性转化为响应式数据

    2-2 data中的某个属性也是对象,把该属性转化为响应式数据

    2-3 数据变化发送通知

    类图结构如下:

    如上图所示:

    walk方法的作用是遍历data中的所有属性,defineReactive是定义响应式数据,也就是通过调用defineReactive方法把属性转化为getter和setter。

    class Observer{
        constructor(data) {
            this.walk(data)
        }
        //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])
            })
        }
        //degineReactivce方法定义响应式数据 把属性转化为getter和setter
        defineReactive(obj,key,val) {
            let that=this
            // 负责收集依赖,并发送通知
            let dep=new Dep()
            //如果val传入对象的话也给对象里面的属性添加getter和setter方法
            this.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
                    //如果给属性重新赋值成对象,给对象里面的属性重新添加getter和setter方法
                    //比如:历史数据vm.msg="Hello World" 修改之后vm.msg={a:'Hwllo World'}
                    //再次调用此方法给vm.msg.a重新添加getter和setter方法
                    that.walk(newValue)
                    //发送通知
                    dep.notify()
                }
            })
        }
    }

    3.Compiler.js功能:

    3-1 负责编译模板,解析指令/差值表达式

    3-2 负责页面的首次渲染

    3-3 当数据变化后重新渲染视图

    类图结构如下:

    如上图所示:

    el为构造函数传过来的options.el,vm是vue的实例,下面都是vm的方法,对DOM进行操作。compile方法内部遍历dom对象的所有节点,并且

    判断这些节点是文本节点,如果是文本节点解析差值表达式,如果是元素节点解析指令,isTextNode和isElementNode方法判断是文本节点还

    是元素节点。compileElement和compileText方法解析差值表达式和指令。isDirective这个方法判断元素属性是否是指令。

    4.Dep.js功能:

    4-1 收集依赖,添加观察者(watcher)

    4-2 通知所有观察者

    如上图所示:

    在vue的响应式机制中,使用观察者模式来响应数据的变化,Dep的作用是收集依赖,在getter方法中收集依赖,在setter方法中通知依赖,每

    一个响应式的属性都会场景一个Dep对象,负责收集所有依赖于该属性的地方,所有依赖于该属性的位置都会创建一个watcher对象,所以

    Dep就是收集于该属性的watcher对象,使用setter方法去通知依赖,当属性发生变化时调用nodify方法去发送通知,然后调用watcher对象

    的update方法。

    类的机构如下图:

    如上图所示:

    subs是一个数组,存储dep中所有的watcher,addSub方法添加watcher,notify方法发布通知

    class Dep{
        constructor() {
            //存储所有的观察者
            this.subs=[]
        }
        // 添加观察者
        addSub(sub){
            if(sub && sub.update) {
                this.subs.push(sub)
            }
        }
        //发送通知
        notify(){
            this.subs.forEach(sub =>{
                sub.update()
            })
        }
    }

     5.Watcher.js功能:

    5-1 当数据变化触发依赖,dep通知所有的Watcher实例更新视图

    5-2 自身实例化的时候往dep对象中添加自己

    如上图所示:

    data中的每一个属性都要创建一个Dep对象, 在收集依赖的时候把所有对象的watcher添加到dep对象的subs数组中,在setter对象中发送通

    知,调用Dep对象的notify方法通知所有关联的watcher对象更新视图。

    类图结构如下:

    如上图所示:

    update对象更新视图,cb回调函数,指明如何更新视图。在更新视图的时候需要一个属性key(data中的属性名称),oldvalue是key 对应的值。

    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)
        }
    }

    下面通过这张图作整体流程的总结:

    ---感谢阅读,o(* ̄︶ ̄*)o开心每一天!
  • 相关阅读:
    HDU_1068_Girls and Boys_二分图匹配
    Bringing Native Performance to Electron
    BIM开发引挈
    旷视科技 -- Face++ 世界最大的人脸识别技术平台
    freedownloadmanager 下载工具,代替 讯雷
    两种 AuthorizationSchemes 在 ASP.NET Core 2
    layout 的应用
    递归获取一个树层次结构(合同清单)的部分选择项(计量合同清单),以及递归删除部分清单
    Nest + typeorm
    http://nancyfx.org + ASPNETCORE
  • 原文地址:https://www.cnblogs.com/websiteblogs/p/15144315.html
Copyright © 2011-2022 走看看