vue响应式系统--observe、watcher、dep

Vue的响应式系统

Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理很是简单直接,咱们能够只关注数据自己,而不用手动处理数据到视图的渲染,避免了繁琐的 DOM 操做,提升了开发效率。vue

vue 的响应式系统依赖于三个重要的类:Dep 类、Watcher 类、Observer 类,而后使用发布订阅模式的思想将他们揉合在一块儿(不了解发布订阅模式的能够看我以前的文章发布订阅模式与观察者模式)。react

Vue的响应式系统

Observer

Observe扮演的角色是发布者,他的主要做用是调用defineReactive函数,在defineReactive函数中使用Object.defineProperty 方法对对象的每个子属性进行数据劫持/监听。express

部分代码展现segmentfault

defineReactive函数,Observe的核心,劫持数据,在setter中向Dep(调度中心)添加观察者,在getter中通知观察者更新。闭包

function defineReactive(obj, key, val, customSetter, shallow){
    //监听属性key
    //关键点:在闭包中声明一个Dep实例,用于保存watcher实例
    var dep = new Dep();

    var getter = property && property.get;
    var setter = property && property.set;
    
    if(!getter && arguments.length === 2) {
        val = obj[key];
    }
    //执行observe,监听属性key所表明的值val的子属性
    var childOb = observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            //获取值
            var value = getter ? getter.call(obj) : val;
            //依赖收集:若是当前有活动的Dep.target(观察者--watcher实例)
            if(Dep.target) {
                //将dep放进当前观察者的deps中,同时,将该观察者放入dep中,等待变动通知
                dep.depend();
                if(childOb) {
                    //为子属性进行依赖收集
                    //其实就是将同一个watcher观察者实例放进了两个dep中
                    //一个是正在自己闭包中的dep,另外一个是子属性的dep
                    childOb.dep.depend();
                }
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            //获取value
            var value = getter ? getter.call(obj) : val;
            if(newVal === value || (newVal !== newVal && value !== value)) {
                return
            }
            if(setter) {
                setter.call(obj, newVal);
            } else {
                val = newVal;
            }
            //新的值须要从新进行observe,保证数据响应式
            childOb = observe(newVal);
            //关键点:遍历dep.subs,通知全部的观察者
            dep.notify();
        }
    });
}

Dep

Dep 扮演的角色是调度中心/订阅器,主要的做用就是收集观察者Watcher和通知观察者目标更新。每一个属性拥有本身的消息订阅器dep,用于存放全部订阅了该属性的观察者对象,当数据发生改变时,会遍历观察者列表(dep.subs),通知全部的watch,让订阅者执行本身的update逻辑。异步

部分代码展现函数

Dep的设计比较简单,就是收集依赖,通知观察者ui

//Dep构造函数
var Dep = function Dep() {
    this.id = uid++;
    this.subs = [];
};
//向dep的观察者列表subs添加观察者
Dep.prototype.addSub = function addSub(sub) {
    this.subs.push(sub);
};
//从dep的观察者列表subs移除观察者
Dep.prototype.removeSub = function removeSub(sub) {
    remove(this.subs, sub);
};
Dep.prototype.depend = function depend() {
    //依赖收集:若是当前有观察者,将该dep放进当前观察者的deps中
    //同时,将当前观察者放入观察者列表subs中
    if(Dep.target) {
        Dep.target.addDep(this);
    }
};
Dep.prototype.notify = function notify() {
    // 循环处理,运行每一个观察者的update接口
    var subs = this.subs.slice();
    for(var i = 0, l = subs.length; i < l; i++) {
        subs[i].update();
    }
};

//Dep.target是观察者,这是全局惟一的,由于在任什么时候候只有一个观察者被处理。
Dep.target = null;
//待处理的观察者队列
var targetStack = [];

function pushTarget(_target) {
    //若是当前有正在处理的观察者,将他压入待处理队列
    if(Dep.target) {
        targetStack.push(Dep.target);
    }
    //将Dep.target指向须要处理的观察者
    Dep.target = _target;
}

function popTarget() {
    //将Dep.target指向栈顶的观察者,并将他移除队列
    Dep.target = targetStack.pop();
}

Watcher

Watcher扮演的角色是订阅者/观察者,他的主要做用是为观察属性提供回调函数以及收集依赖(如计算属性computed,vue会把该属性所依赖数据的dep添加到自身的deps中),当被观察的值发生变化时,会接收到来自dep的通知,从而触发回调函数。,this

部分代码展现lua

Watcher类的实现比较复杂,由于他的实例分为渲染 watcher(render-watcher)、计算属性 watcher(computed-watcher)、侦听器 watcher(normal-watcher)三种,
这三个实例分别是在三个函数中构建的:mountComponent 、initComputed和Vue.prototype.$watch。

normal-watcher:咱们在组件钩子函数watch 中定义的,都属于这种类型,即只要监听的属性改变了,都会触发定义好的回调函数,这类watch的expression是咱们写的回调函数的字符串形式。

computed-watcher:咱们在组件钩子函数computed中定义的,都属于这种类型,每个 computed 属性,最后都会生成一个对应的 watcher 对象,可是这类 watcher 有个特色:当计算属性依赖于其余数据时,属性并不会当即从新计算,只有以后其余地方须要读取属性的时候,它才会真正计算,即具有 lazy(懒计算)特性。这类watch的expression是计算属性中的属性名。

render-watcher:每个组件都会有一个 render-watcher, 当 data/computed 中的属性改变的时候,会调用该 render-watcher 来更新组件的视图。这类watch的expression是 function () {vm._update(vm._render(), hydrating);}

除了功能上的区别,这三种 watcher 也有固定的执行顺序,分别是:computed-render -> normal-watcher -> render-watcher

这样安排是有缘由的,这样就能尽量的保证,在更新组件视图的时候,computed 属性已是最新值了,若是 render-watcher 排在 computed-render 前面,就会致使页面更新的时候 computed 值为旧数据。

这里咱们只看其中一部分代码

function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
    this.vm = vm;
    if(isRenderWatcher) {
        vm._watcher = this;
    }
    vm._watchers.push(this);
    // options
    if(options) {
        this.deep = !!options.deep; //是否启用深度监听
        this.user = !!options.user; //主要用于错误处理,侦听器 watcher的 user为true,其余基本为false
        this.lazy = !!options.lazy; //惰性求职,当属于计算属性watcher时为true
        this.sync = !!options.sync; //标记为同步计算,三大类型暂无
    } else {
        this.deep = this.user = this.lazy = this.sync = false;
    }
    //初始化各类属性和option
    
    //观察者的回调
    //除了侦听器 watcher外,其余大多为空函数
    this.cb = cb;
    this.id = ++uid$1; // uid for batching
    this.active = true;
    this.dirty = this.lazy; // for lazy watchers
    this.deps = [];
    this.newDeps = [];
    this.depIds = new _Set();
    this.newDepIds = new _Set();
    this.expression = expOrFn.toString();
    // 解析expOrFn,赋值给this.getter
    // 当是渲染watcher时,expOrFn是updateComponent,即从新渲染执行render(_update)
    // 当是计算watcher时,expOrFn是计算属性的计算方法
    // 当是侦听器watcher时,expOrFn是watch属性的名字,this.cb就是watch的handler属性
    
    //对于渲染watcher和计算watcher来讲,expOrFn的值是一个函数,能够直接设置getter
    //对于侦听器watcher来讲,expOrFn是watch属性的名字,会使用parsePath函数解析路径,获取组件上该属性的值(运行getter)
    
    //依赖(订阅目标)更新,执行update,会进行取值操做,运行watcher.getter,也就是expOrFn函数
    if(typeof expOrFn === 'function') {
        this.getter = expOrFn;
    } else {
        this.getter = parsePath(expOrFn);
    }
    this.value = this.lazy ? undefined : this.get();
};    
//取值操做
Watcher.prototype.get = function get() {
    //Dep.target设置为该观察者
    pushTarget(this);
    var vm = this.vm;
    //取值
    var value = this.getter.call(vm, vm);
    //移除该观察者
    popTarget();
    return value
};
Watcher.prototype.addDep = function addDep(dep) {
    var id = dep.id;
    if(!this.newDepIds.has(id)) {
        //为观察者的deps添加依赖dep
        this.newDepIds.add(id);
        this.newDeps.push(dep);
        if(!this.depIds.has(id)) {
            //为dep添加该观察者
            dep.addSub(this);
        }
    }
};
//当一个依赖改变的时候,通知它update
Watcher.prototype.update = function update() {
    //三种watcher,只有计算属性 watcher的lazy设置了true,表示启用惰性求值
    if(this.lazy) {
        this.dirty = true;
    } else if(this.sync) {
        //标记为同步计算的直接运行run,三大类型暂无,因此基本会走下面的queueWatcher
        this.run();
    } else {
        //将watcher推入观察者队列中,下一个tick时调用。
        //也就是数据变化不是当即就去更新的,而是异步批量去更新的
        queueWatcher(this);
    }
};

//update执行后,运行回调cb
Watcher.prototype.run = function run() {
    if(this.active) {
        var value = this.get();
        if(
            value !== this.value ||
            isObject(value) ||
            this.deep
        ) {
            var oldValue = this.value;
            this.value = value;
            //运行 cb 函数,这个函数就是以前传入的watch中的handler回调函数
            if(this.user) {
                try {
                    this.cb.call(this.vm, value, oldValue);
                } catch(e) {
                    handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
                }
            } else {
                this.cb.call(this.vm, value, oldValue);
            }
        }
    }
};

//对于计算属性,当取值计算属性时,发现计算属性的watcher的dirty是true
//说明数据不是最新的了,须要从新计算,这里就是从新计算计算属性的值。
Watcher.prototype.evaluate = function evaluate() {
    this.value = this.get();
    this.dirty = false;
};

//收集依赖
Watcher.prototype.depend = function depend() {
    var this$1 = this;

    var i = this.deps.length;
    while(i--) {
        this$1.deps[i].depend();
    }
};

总结

Observe是对数据进行监听,Dep是一个订阅器,每个被监听的数据都有一个Dep实例,Dep实例里面存放了N多个订阅者(观察者)对象watcher。

被监听的数据进行取值操做时(getter),若是存在Dep.target(某一个观察者),则说明这个观察者是依赖该数据的(如计算属性中,计算某一属性会用到其余已经被监听的数据,就说该属性依赖于其余属性,会对其余属性进行取值),就会把这个观察者添加到该数据的订阅器subs里面,留待后面数据变动时通知(会先经过观察者id判断订阅器中是否已经存在该观察者),同时该观察者也会把该数据的订阅器dep添加到自身deps中,方便其余地方使用。

被监听的数据进行赋值操做时(setter)时,就会触发dep.notify(),循环该数据订阅器中的观察者,进行更新操做。

相关文章
相关标签/搜索