• vue-双向响应数据底层原理分析


    总所周知,vue的一个大特色就是实现了双向数据响应,数据改变,视图中引用该数据的部分也会自动更新

    一.双向数据绑定基本思路

    “数据改变,视图中引用该数据的部分也会自动更新“,从这句话,我们可以分析出以下几点:

    • 数据要绑定监听事件
    • 视图要绑定更新事件
    • 监听事件会触发更新事件

    vue的双向数据响应也是基于以上思路开发的

    二.vue基于数据劫持+发布订阅模式实现双向绑定

    2.1数据劫持

    利用Object.defineProperty深度遍历每个响应数据,给每个响应数据和其子属性添加get和set方法.

    Object.defineProperty的get方法在数据被调用时执行.所以get()用于收集依赖,所谓的依赖就是使用当前响应数据的dom

    Object.defineProperty的set方法在数据修改时被执行.所以set()方法用于通知监听事件,我的数据更新了defaultReative(data,key,value)     

    defaultReative(data,key,value){
            //先去做递归
            this.observer(value);
            //给每个响应数据的get方法上都添加一个dep实例
            var dep = new Dep();
            //数据劫持
            Object.defineProperty(data,key,{
                get(){
                     /*在渲染html的时候会调用响应数据,也就执行了get方法;watcher实例是订阅者,渲染时会给当前dom初始化一个watcher实例,也就是给当前的dom添加了订阅者,
             * 订阅者内部会有更新dom的方法,数据变化的时候,发布者通知订阅者,订阅者内部去调用更新dom的方法.
    
             * watcher内部有dep.target赋值操作,赋值为watcher本身,
              这样才能确保有我们收集的依赖就是调用了当前响应数据的订阅者.
             */
                    Dep.target && dep.addDep(Dep.target);
                    return value;
                },
                set(newVal){
                    if(newVal == value)return;
                    //赋值操作 当外部设置data身上的某个属性的时候 将新值赋值给旧值
                    value = newVal;
                    
                    dep.notify();
                   
                }
            })
        }

    dep是发布订阅模式的一环,会在下面讲解

    2.2发布订阅模式

    发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案.这种模式让一个对象不在显示地调用另一个对象的接口,实现了多个对象松耦合联系在一起.

    只需要订阅感兴趣的事件发生点,当事件执行时就会通知给所有订阅者.

    一个简单的发布-订阅模式的实现主要是以下三点内容:

    • 指定好发布者;
    • 发布者有一个缓存列表,里面存放了回调函数,以便发布后通知订阅者;
    • 发布消息的时候遍历缓存列表,依次触发订阅者的回调

    在vue中dep就类似发布者,下面是dep的实现

    class Dep{
        constructor(){
            this.deps = [];
        }
        addDep(dep){
            this.deps.push(dep);
        }
        notify(){       
            this.deps.forEach((item)=>{
                item.update();
            })
        }
    }

    我们给每一个响应数据get()内部添加一个deps列表,列表内部就是调用该响应数据的订阅者,

    我们给每一个响应数据set()内部添加一个dep.notify(),这个方法去通知订阅者调用内部的更新函数,更新视图

    defaultReative(data,key,value){
            //先去做递归
            this.observer(value);
            //给每个响应数据的get方法上都添加一个dep实例
            var dep = new Dep();
            //数据劫持
            Object.defineProperty(data,key,{
                get(){
                     /*在渲染html的时候会调用响应数据,也就执行了get方法;watcher实例是订阅者,渲染时会给当前dom初始化一个watcher实例,也就是给当前的dom添加了订阅者,
             * 订阅者内部会有更新dom的方法,数据变化的时候,发布者通知订阅者,订阅者内部去调用更新dom的方法.
    
             * watcher内部有dep.target赋值操作,赋值为watcher本身,
              这样才能确保有我们收集的依赖就是调用了当前响应数据的订阅者
             */
                    Dep.target && dep.addDep(Dep.target);
                    return value;
                },
                set(newVal){
                    if(newVal == value)return;
                    //赋值操作 当外部设置data身上的某个属性的时候 将新值赋值给旧值
                    value = newVal;
                    
                    dep.notify();
                   
                }
            })
        }
    watcher

    watcher类似订阅者,他会和dom去做关联.watcher内部有一个update方法,用于更新dom节点.

    watcher也会跟Dep做关联,在constructor中,调用dep.target=this,目的是让Dep指向当前的watcher.响应数据对应的订阅者不同,每个响应数据收集到对应的依赖就需要指定this,因为在渲染dom的时候,我们会给每个dom节点都初始化一个watcher实例,this就是这个watcher实例

    下面是一个watcher方法的实现

    //监听数据的变化
    class Watcher{
        constructor(vm,exp,callback){
            this.$vm = vm;
            this.$exp = exp;
            this.callback = callback;
            //给dep添加了一个静态属性target,值是watcher自己
            Dep.target = this;
            //触发属性的getter方法
            this.$vm[this.$exp];
            Dep.target = null;
        }
        //在index.js中一旦数据有变动会触发dep.notify方法,
            //nodity会遍历dep中收集的watcher,调用watcher中的update方法,在update方法中执行,
            //这里(compile)传递给watcher的方法(updateFn)更新视图
        update(){
            //视图更新
            this.callback.call(this.$vm,this.$vm[this.$exp])
        }
    }

    在渲染dom的时候,将dom方法的更新函数传递给watcher做回调

     update(el,vm,exp,type){
            //为了其他的一些指令需要的一些公共的逻辑在这个方法里面编写
            //比如这里是text指令,updateFn=textupdate
            var updateFn = this[type+'update'];
            //如果textupdate方法存在,就去执行textupdate方法
            updateFn && updateFn(el,vm[exp]);
            //给每个节点都添加上监听
            //添加监听数据的订阅者(给watcher传递数据和回调),在index.js中一旦数据有变动会触发dep.notify方法,
            //nodity会遍历dep中收集的watcher,调用watcher中的update方法,在update方法中执行,
            //这里(compile)传递给watcher的方法(updateFn)更新视图
            new Watcher(vm,exp,(value)=>{
                updateFn && updateFn(el,value);
            });
        }

    总结.

    vue在编译html模版的时候,会给每个dom节点初始化watcher订阅者,把该节点的更新函数传递给watcher类中

    vue在初始化时会深度遍历每一个响应属性,给每个响应属性添加上get和set方法,当html渲染时用到了响应数据就去调用get方法得到数据,同时get内部通过依赖收集知道了调用的订阅者都有那些;

    在修改响应数据时会调用set方法,在方法内部通过dep.notify通知订阅者,订阅者去调用update更新dom节点

  • 相关阅读:
    解决Maven下载依赖慢
    Spring Boot系列教程六:日志输出配置log4j2
    Spring Boot系列教程三:使用devtools实现热部署
    Spring Boot系列教程五:使用properties配置文件实现多环境配置
    Spring Boot系列教程四:配置文件详解properties
    Spring Boot系列教程二:创建第一个web工程 hello world
    Spring Boot系列教程一:Eclipse安装spring-tool-suite插件
    Spring Boot系列教程十:Spring boot集成MyBatis
    vim入门一 常用指令
    Linux IO多路复用之epoll网络编程(含源码)
  • 原文地址:https://www.cnblogs.com/liuXiaoDi/p/12742820.html
Copyright © 2020-2023  润新知