V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
answershuto
V2EX  ›  前端开发

Vue.js 依赖收集

  •  
  •   answershuto ·
    answershuto · 2017-08-28 09:38:14 +08:00 · 1636 次点击
    这是一个创建于 2674 天前的主题,其中的信息可能已经有所发展或是发生改变。

    写在前面

    因为对 Vue.js 很感兴趣,而且平时工作的技术栈也是 Vue.js ,这几个月花了些时间研究学习了一下 Vue.js 源码,并做了总结与输出。 文章的原地址:https://github.com/answershuto/learnVue。 在学习过程中,为 Vue 加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习 Vue 源码的小伙伴有所帮助。 可能会有理解存在偏差的地方,欢迎提 issue 指出,共同学习,共同进步。

    依赖收集是在响应式基础上进行的,不熟悉的同学可以先了解《响应式原理》

    为什么要依赖收集

    先看下面这段代码

    new Vue({
        template: 
            `<div>
                <span>text1:</span> {{text1}}
                <span>text2:</span> {{text2}}
            <div>`,
        data: {
            text1: 'text1',
            text2: 'text2',
            text3: 'text3'
        }
    });
    

    按照之前《响应式原理》中的方法进行绑定则会出现一个问题—— text3 在实际模板中并没有被用到,然而当 text3 的数据被修改的时候( this.text3 = 'test')的时候,同样会触发 text3 的 setter 导致重新执行渲染,这显然不正确。

    先说说 Dep

    当对 data 上的对象进行修改值的时候会触发它的 setter,那么取值的时候自然就会触发 getter 事件,所以我们只要在最开始进行一次 render,那么所有被渲染所依赖的 data 中的数据就会被 getter 收集到 Dep 的 subs 中去。在对 data 中的数据进行修改的时候 setter 只会触发 Dep 的 subs 的函数。

    定义一个依赖收集类 Dep。

    class Dep () {
        constructor () {
            this.subs = [];
        }
    
        addSub (sub: Watcher) {
            this.subs.push(sub)
        }
    
        removeSub (sub: Watcher) {
            remove(this.subs, sub)
        }
    
        notify () {
            // stabilize the subscriber list first
            const subs = this.subs.slice()
            for (let i = 0, l = subs.length; i < l; i++) {
                subs[i].update()
            }
        }
    }
    

    Watcher

    订阅者,当依赖收集的时候回 addSub 到 sub 中,在修改 data 中数据的时候会触发 Watcher 的 notify,从而回调渲染函数。

    class Watcher () {
        constructor (vm, expOrFn, cb, options) {
            this.cb = cb;
            this.vm = vm;
    
            /*在这里将观察者本身赋值给全局的 target,只有被 target 标记过的才会进行依赖收集*/
            Dep.target = this;
    
            /*触发渲染操作进行依赖收集*/
            this.cb.call(this.vm);
        }
    
        update () {
            this.cb.call(this.vm);
        }
    }
    

    开始依赖收集

    class Vue {
        constructor(options) {
            this._data = options.data;
            observer(this._data, options.render);
            let watcher = new Watcher(this, );
        }
    }
    
    function defineReactive (obj, key, val, cb) {
        /*在闭包内存储一个 Dep 对象*/
        const dep = new Dep();
    
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=>{
                if (Dep.target) {
                    /*Watcher 对象存在全局的 Dep.target 中*/
                    dep.addSub(Dep.target);
                }
            },
            set:newVal=> {
                /*只有之前 addSub 中的函数才会触发*/
                dep.notify();
            }
        })
    }
    
    Dep.target = null;
    

    将观察者 Watcher 实例赋值给全局的 Dep.target,然后触发 render 操作只有被 Dep.target 标记过的才会进行依赖收集 。有 Dep.target 的对象会讲 Watcher 的实例 push 到 subs 中,在对象被修改出发 setter 操作的时候 dep 会调用 subs 中的 Watcher 实例的 update 方法进行渲染。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3190 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 105ms · UTC 12:36 · PVG 20:36 · LAX 04:36 · JFK 07:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.