Vue响应式原理-理解Observer、Dep、Watcher

开篇

最近在学习Vue的源码,看了网上一些大神的博客,看起来感受仍是蛮吃力的。本身记录一下学习的理解,但愿可以达到简单易懂,不看源码也能理解的效果😆javascript

若是有错误,恳求大佬们指点嘿😋html

Object.defineProperty

相信不少同窗或多或少都了解Vue的响应式原理是经过Object.defineProperty实现的。被Object.defineProperty绑定过的对象,会变成「响应式」化。也就是改变这个对象的时候会触发get和set事件。进而触发一些视图更新。举个栗子🌰vue

function defineReactive (obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
            console.log('我被读了,我要不要作点什么好?');
            return val;
        },
        set: newVal => {
            if (val === newVal) {
                return;
            }
            val = newVal;
            console.log("数据被改变了,我要把新的值渲染到页面上去!");
        }
    })
}

let data = {
    text: 'hello world',
};

// 对data上的text属性进行绑定
defineReactive(data, 'text', data.text);

console.log(data.text); // 控制台输出 <我被读了,我要不要作点什么好?>
data.text = 'hello Vue'; // 控制台输出 <hello Vue && 数据被改变了,我要把新的值渲染到页面上去!>
复制代码

Observer 「响应式」

Vue中用Observer类来管理上述响应式化Object.defineProperty的过程。咱们能够用以下代码来描述,将this.data也就是咱们在Vue代码中定义的data属性所有进行「响应式」绑定。java

class Observer {
    constructor() {
        // 响应式绑定数据经过方法
    	observe(this.data);
    }
}

export function observe (data) {
    const keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
       // 将data中咱们定义的每一个属性进行响应式绑定
       defineReactive(obj, keys[i]);
    }
}
复制代码

Dep 「依赖管理」

什么是依赖?

相信没有看过源码或者刚接触Dep这个词的同窗都会比较懵。那Dep到底是用来作什么的呢? 咱们经过defineReactive方法将data中的数据进行响应式后,虽然能够监听到数据的变化了,那咱们怎么处理通知视图就更新呢?git

Dep就是帮咱们收集【究竟要通知到哪里的】。好比下面的代码案例,咱们发现,虽然data中有textmessage属性,可是只有message被渲染到页面上,至于text不管怎么变化都影响不到视图的展现,所以咱们仅仅对message进行收集便可,能够避免一些无用的工做。github

那这个时候messageDep就收集到了一个依赖,这个依赖就是用来管理datamessage变化的。异步

<div>
    <p>{{message}}</p>
</div>
复制代码
data: {
    text: 'hello world',
    message: 'hello vue',
}
复制代码

当使用watch属性时,也就是开发者自定义的监听某个data中属性的变化。好比监听message的变化,message变化时咱们就要通知到watch这个钩子,让它去执行回调函数。函数

这个时候messageDep就收集到了两个依赖,第二个依赖就是用来管理watchmessage变化的。post

watch: {
    message: function (val, oldVal) {
        console.log('new: %s, old: %s', val, oldVal)
    },
}        
复制代码

当开发者自定义computed计算属性时,以下messageT属性,是依赖message的变化的。所以message变化时咱们也要通知到computed,让它去执行回调函数。 这个时候messageDep就收集到了三个依赖,这个依赖就是用来管理computedmessage变化的。学习

computed: {
    messageT() {
        return this.message + '!';
    }
}
复制代码

图示以下:一个属性可能有多个依赖,每一个响应式数据都有一个Dep来管理它的依赖。

依赖收集

如何收集依赖

咱们如何知道data中的某个属性被使用了,答案就是Object.defineProperty,由于读取某个属性就会触发get方法。能够将代码进行以下改造:

function defineReactive (obj, key, val) {
    let Dep; // 依赖

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
            console.log('我被读了,我要不要作点什么好?');
            // 被读取了,将这个依赖收集起来
            Dep.depend(); // 本次新增
            return val;
        },
        set: newVal => {
            if (val === newVal) {
                return;
            }
            val = newVal;
            // 被改变了,通知依赖去更新
            Dep.notify(); // 本次新增
            console.log("数据被改变了,我要把新的值渲染到页面上去!");
        }
    })
}
复制代码

什么是依赖

那所谓的依赖到底是什么呢?上面的图中已经暴露了答案,就是Watcher

Watcher 「中介」

Watcher就是相似中介的角色,好比message就有三个中介,当message变化,就通知这三个中介,他们就去执行各自须要作的变化。

Watcher可以控制本身属于哪一个,是data中的属性的仍是watch,或者是computedWatcher本身有统一的更新入口,只要你通知它,就会执行对应的更新方法。

所以咱们能够推测出,Watcher必需要有的2个方法。一个就是通知变化,另外一个就是被收集起来到Dep中去。

class Watcher {
    addDep() {
        // 我这个Watcher要被塞到Dep里去了~~
    },
    update() {
        // Dep通知我更新呢~~
    }, 
}
复制代码

总结

回顾一下,Vue响应式原理的核心就是ObserverDepWatcher

Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher

在数据被改的时候,触发set方法,经过对应的全部依赖(Watcher),去执行更新。好比watchcomputed就执行开发者自定义的回调方法。

本篇文章属于入门篇,可以先简单的理解ObserverDepWatcher三者的做用和关系。后面会逐渐详细和深刻,按部就班的理解和学习。

若是你以为对你有帮助,就点个赞吧~

正在书写的系列~

Github博客 欢迎交流~

相关文章
相关标签/搜索