美文网首页
深入理解和手写--Vue响应式原理/剖析Dep与Wacher关系

深入理解和手写--Vue响应式原理/剖析Dep与Wacher关系

作者: Raral | 来源:发表于2021-01-22 09:56 被阅读0次

    响应式原理

    数据绑定(效果)

    1. 效果
      一旦更新了data中的某个属性数据,所有界面上直接使用或间接使用了此属性的节点都会更新。

    2. 数据劫持(实现的方式)

    • 数据劫持是vue中用来实现数据绑定的一种技术。
    • 基本思想: 通过defineProperty()来监视data中所有的属性(任意层次)数据的变化,一旦变化就去更新界面。
      注意
    • 数据代理是针对vm, 数据绑定针对data。

    实现响应式主要核心对象

    • Dep对象 和 Watcher对象

    其实从因为单词意思中是依赖,观察;
    vue中依赖的意思:数据和模板表达式依赖关系,一 对 多。
    vue中观察的意思:模板表达式和数据的关系, 一 对 多。

    由于vue要是实现数据和视图双向绑定,必须要监听数据改变,也要监听视图变化,从而定义两个核心对象,Dep对象收集依赖,Wacher对象监听视图 模板表达式。


    整体流程图.png

    下面详细介绍两个对象咋样关联的,它们之间到底存在什么联系

    Dep watcher
    初始化什么时候参数? 在observe.js中监听data属性中的某一个属性,会自动生成一个Dep对象 在complie.js中模板编译初始化中bind方法,自动产生一个Watcher对象
    初始对象核心代码位置? dep.png watcher.png
    初始化对象对象中属性作用? dep-key.png watcher-key.png
    什么时候开始依赖收集? 当模板编译时通过表达式读取数据时候,进入observe的set方法
    收集依赖核心方法流程? 读取数据=> defineObserve的get方法 => Dep.depend => Watcher.addDep watcher-addDep.jpg

    |当数据变化了咋样通知视图更新?|设置数据 => defineObserve的set方法 => Dep.notify => 遍历watcher.update => 调用传过来的更新节点cb => 从而更新视图最新状态数据 |

    注意

    • dep 主要针对 data里面数据的管理; watcher 主要针对 模板中的表达式的管理
    • dep 与 watcher 的关系是双向的,也就是代表 数据 和视图双向的。通过一个dep可以观看到多个watcher,通过一个watcher可以观看到多个dep; 一层套一层。


      dep-data.png

    手写简易 响应式原理

    • MVVM入口js
    (function() {
        function Vue(options) {
            // 配置对象保存到vm
            this.$options = options || {};
            var data = this._data = this.$options.data;
            var me = this;
            //数据代理 vm.xxx => vm._data.xxx 不需要递归
            this.convertData(data);
    
            // 响应式核心类 发布订阅模式--数据劫持--依赖收集
            observe(data);
    
            //模板编译 核心类
            this.$compile = new Compile(options.el || document.body, this);
    
        }
    
        Vue.prototype = {
            constructor: Vue,
            convertData: function(data) {
                var me = this;
                Object.keys(data).forEach(key => {
                    me._proxyData(key);
                })
            },
            _proxyData: function(key) {
                var me = this;
                Object.defineProperty(me, key, {
                    configurable: false,
                    enumerable: true,
                    // writable: true,
                    get: function proxyGetter() {
                        return me._data[key];
                    },
                    set: function proxySetter(newVal) {
                        me._data[key] = newVal;
                    }
                })
            }
    
    
        }
        return window.Vue = Vue;
    })()
    
    • observe.js
    function Observe(data) {
        this.data = data;
        // 初始化
        this.init(data);
    }
    Observe.prototype = {
        constructor: Observe,
        init: function(data) {
            let me = this;
            //将所有属性进行劫持
            Object.keys(data).forEach(key => {
                me.defineReactive(me.data,key,data[key]);
            })
        },
         // 暂时不考虑数组
        defineReactive: function(data, key, val) {
            // console.log(data, key, val)
            // 一个vm实例对象 => 一个observe对象
            //一个vm实例对象中的data对象每一个属性 => dep对象
            var dep = new Dep();
            //间接递归: 目的 把 所有的属性都生成一个 dep对象和添加set/get方法
            var childObj = observe(val);
    
            Object.defineProperty(data, key, {
                enumerable:true,
                configurable:false,
                get: function() {
                    //收集依赖作用就是 与 watcher建立关系的
                    //作用就是在watcher对象没生成之前不用建立关系
                    //咋样确定watcher对象是否建立的呢?
                    // 就是通过在Dep对象属性target判断是否存在watcher并且指向当前watcher
                    if(Dep.target) {
                        dep.depend();
                        // console.log("observe的get方法watcher产生 ",Dep.target)
                    }
                    // console.log("observe的get方法watcher没有产生 ",Dep.target)
                    return val;
                },
                set: function(newVal) {
                    if(newVal == val) return;
                    val = newVal;
                    //如果新值是对象 进去监听
                    childObj = observe(newVal);
    
                    //针对 数据改变了 要通知有关系的wacher对象,然后让它 去 更新视图
                    //通知多个 订阅者,因为dep对象 有一个属性subs数组,一 对 多
                    dep.notify();
                }
            })
        }
    
    
    }
    
    
    var uid = 0;
    function Dep() {
        this.id = uid ++;
        this.subs = [];
    }
    Dep.target = null; // 记录当前this指向
    Dep.prototype = {
        constructor: Dep,
        // watcher 与 dep建立关系
        depend: function() {
            // 把dep添加到watcher
            Dep.target.addDep(this);
        },
        // 添加watcher
        addSub: function(watcher) {
            this.subs.push(watcher);
        },
        //通知 watcher,让watcher 去 更新界面
        notify: function() {
            this.subs.forEach(watcher => {
                watcher.update();
            })
        },
       
    }
    
    
    
    function observe(value) {
        // 判断是否是对象
        if(!value || typeof value !== "object") return;
        return new Observe(value);
    }
    
    
    • watcher.js
    function Watcher(vm, exp, cb) {
        this.cb = cb;
        this.exp = exp;
        this.vm = vm;
        //先定义depIds,在获取值,否则会报错
        this.depIds = {};
    
        this.value = this.get();
    
    
    }
    Watcher.prototype = {
        consructor: Watcher,
        get: function() {
            // 把当前watcher对象赋值给 Dep.target属性
            Dep.target = this;
            // console.log(this)
            // var value = this.parseGetter(this.exp);
            var value = this.vm._data[this.exp];
            Dep.target = null;
            return value;
            // console.log(this,value);
        },
        addDep: function(dep) {
            // console.log(this,dep)
            // 判断是否建立过关系
            //初始化编译模板时候,当读取vm数据时候 就建立关系
            if(!this.depIds.hasOwnProperty(dep.id)) {
                // 把watcher 添加到dep 中的subs , dep 与 watcher 关系 一对多, 
                //模板中有几个表达式,就添加几个watcher
                dep.addSub(this);
                //把dep 添加到 watcher中的depIds, watcher 与 dep 关系 一对多
                // 表达式有多少层级,就有几个dep
                this.depIds[dep.id] = dep;
    
                console.log(this,dep)
            }
    
    
            // 注意: dep 和 watcher对象之间 方法有通讯方式有两种
            //1. 通过函数参数 进行 关联
            //2. 通过函数this 进行 关联
    
        },
        // 更新操作
        update: function() {
            //获取最新值
            var newVal = this.get();
            var oldVal = this.value;
            console.log(newVal, oldVal);
            if(newVal !== oldVal) {
                this.value = newVal;
                this.cb.call(this.vm, newVal, oldVal);
            }
            
        },
        //处理层级嵌套去访问属性 a.b.c
        parseGetter: function(exp) {
            if(/\./.test(exp)) {
                let val;
                let exps = exp.split(".");
                exps.forEach(exp => {
                    val = this.recur(exp)
                })
                return value;
    
    
            }else {
                return this.vm._data[exp]
            }
        },
        //递归
        recur: function(exp) {
            let value = this.vm._data[exp];
            if(typeof value === "object") {
                this.recur(value);
            }
        }
    }
    
    
    

    相关文章

      网友评论

          本文标题:深入理解和手写--Vue响应式原理/剖析Dep与Wacher关系

          本文链接:https://www.haomeiwen.com/subject/lbhszktx.html