美文网首页
深入理解和手写--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