平凡之路。

Vue依赖绑定

Date: Author: ZJ

本文章采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。

按照之前响应式原理中的绑定方式会存在一个问题,那就是当前实例的所有属性发生改变时它的setter都会触发,即使这个属性并没有被使用过。

依赖绑定

Dep(dependence)

既然属性被修改时会触发其对应的setter,那么当他被使用时自然也会触发其对应的getter。 简单实现:首先定义一个Dep类,它维护一个订阅者数组, 提供addSub,removeSub,notify方法

class Dep {
    constructor() {
        this.subs = []
    }
    addSub(sub) {
        this.subs.push(sub)
    }
    removeSub(sub) {
        let _arr = this.subs;
        _arr.splice(_arr.findIndex(v => v === sub))
    }
    // 订阅者必须提供update方法。
    notify() {
        for(let sub of this.subs) {
            sub.update();
        }
    }
}

Watcher

订阅者,当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触dep对象的notify,通知所有Watcher对象触发update方法。

    class Watch {
        constructor(vm, cb) {
            this._cb = cb;
            this.vm = vm;
            Dep.target = this;
            this.update();
        }
        update() {
            this._cb.apply(this.vm)
        }
    }

依赖收集

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

    class Mvvm {
        constructor(options) {
            //...
            this.watcher = newWatcher(this.vm, this.cb);
        }
    };
    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.targe    t中*/
                    dep.addSub(Dep.target);
                }
            },
            set:newVal=> {
                /*只有之前addSub中的函数才会触发*/
                dep.notify();
            }
        })
}

完整代码

        function observer(obj, cb) {
            if (typeof obj !== 'object' || !obj) {
                return;
            }
            Object.keys(obj).forEach(key => {
                defineReactive(obj, key, obj[key], cb)
            })
        }

        function defineReactive(obj, key, val, cb) {
            observer(val);
            let dep = new Dep();
            Object.defineProperty(obj, key, {
                enumerable: true,
                configurable: true,
                get: () => {
                    if(Dep.target) {
                        dep.addSub(Dep.target)
                    }
                    return val;
                },
                set: newVal => {
                    dep.notify();
                    return newVal
                }
            })
        }

        function _proxy(data, vm) {
            Object.keys(data).forEach(key => {
                Object.defineProperty(vm, key, {
                    enumerable: true,
                    configurable: true,
                    get: () => vm._data[key],
                    set: newVal => {
                        vm._data[key] = newVal
                    }
                })
            })
        };
        Dep.target = null;
        class Mvvm {
            constructor(options) {
                this._data = options.data;
                this.cb = options.render;
                _proxy(this._data, this);
                observer(this._data, this.cb)
                this.watcher = new Watcher(this.vm, '123' ,this.cb);
            }
        };
        class Dep {
            constructor() {
                this.subs = [];
            }
            addSub(sub) {
                this.subs.push(sub)
            }
            removeSub(sub) {
                this.subs.splice(this.subs.findIndex(v => v === sub));
            }
            notify() {
                this.subs.forEach(v => {
                    v.update();
                })
            }
        }
        class Watcher {
            constructor(vm, expOrfn, cb, options) {
                this.vm = vm;
                this.cb = cb;
                Dep.target = this;
            }
            update() {
                this.cb.apply(this.vm)
            }
        }

对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。