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

响应式原理

数据绑定(效果)

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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容