vue中的双向数据绑定mvvm

详细文档可参考 https://github.com/DMQ/mvvm

总体思路

  1. 入口调用observer, 对于每一个property创建一个Dep, get方法负责将watcher添加到Dep中,set方法负责通知Dep中所有的watcher进行update操作
  2. 入口接着调用compile,遍历节点,找到所有含vue指令的节点,从modal中获取数据并渲染或者添加合适的监听事件,并创建watcher,wathcher的构造函数中会调用自身get,自身get函数首先将自身赋值给Dep.target,随后调用vue中property的get方法完成将watcher添加到Dep中的操作,另外watcher中的update方法会执行从compile传入的update的回调函数

主要分四个模块Observer, Dep, Compile, Watch

Observer: 进行数据劫持,并且通过Dep通知watchList进行更新

Object.defineProperty(data, key, {
    var dep = new Dep();
    get: function() {
        Dep.target && dep.addDep(Dep.target); // 供watcher创建时调用
        return val;
    }
        set: function(newVal) {
            if (val === newVal) return
                val = newVal;
                dep.notify(); // 通知所有订阅者
        }
});

Dep:为不同的属性创建唯一的watcherList,用来存放属性的监听者

function Dep() {
    this.subs = [];
}
Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },
    notify: function() {
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
};

Compile: 解析模板指令,绑定监听,替换数据,以及创建watcher(fragment)

bind: function(node, vm, exp, dir) {
        var updaterFn = updater[dir + 'Updater'];
        // 第一次初始化视图
        updaterFn && updaterFn(node, vm[exp]);
        // 实例化订阅者,此操作会在对应的属性消息订阅器中添加了该订阅者watcher
        new Watcher(vm, exp, function(value, oldValue) {
            // 一旦属性值有变化,会收到通知执行此更新函数,更新视图
            updaterFn && updaterFn(node, value, oldValue);
        });
    }

watcher: 向Dep中添加自己,调用complie中传入给自己的回调函数

function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    // 此处为了触发属性的getter,从而在dep添加自己,结合Observer更易理解
    this.value = this.get(); 
}
Watcher.prototype = {
    update: function() {
        this.run(); // 属性值变化收到通知
    },
    run: function() {
        var value = this.get(); // 取到最新值
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal); // 执行Compile中绑定的回调,更新视图
        }
    },
    get: function() {
        Dep.target = this;  // 将当前订阅者指向自己
        var value = this.vm[exp];   // 触发getter,添加自己到属性订阅器中
        Dep.target = null;  // 添加完毕,重置
        return value;
    }
};

最终入口(proxy)

observe(data, this);
this.$compile = new Compile(options.el || document.body, this)

你可能感兴趣的:(vue中的双向数据绑定mvvm)