美文网首页
Vue响应式原理(Object.defineProperty)全

Vue响应式原理(Object.defineProperty)全

作者: zpkzpk | 来源:发表于2019-03-16 09:16 被阅读0次

    大致流程

    1. 发生在beforeCreate和created之间initState(vm)中的defineProperty
    2. 发生在beforeMount和mounted之间的Dep和Watcher的初始化
    3. 发生在beforeUpdate前到updated触发,这期间Watcher的相关变化

    第一步:数据初始化

    在new一个Vue实例时,其实只执行了一个this._init(options)

    function Vue (options) {
      if (process.env.NODE_ENV !== 'production' &&
        !(this instanceof Vue)
      ) {
        warn('Vue is a constructor and should be called with the `new` keyword');
      }
      this._init(options);
    }
    

    在_init方法中,包含以下这些操作,其中initState包含对data和props的处理过程;initData包含了对data创建观察者的observe函数

    Vue.prototype._init = function (options) {
        ...
        initLifecycle(vm);
        initEvents(vm);
        initRender(vm);
        callHook(vm, 'beforeCreate');
        initInjections(vm); 
        initState(vm);
        initProvide(vm);
        callHook(vm, 'created');
        ...
    }
    function initState (vm) {
        ...
        if (opts.data) {
            initData(vm);
        } else {
            observe(vm._data = {}, true);
        ...
    }
    function initData (vm) {
        var data = vm.$options.data;
        data = vm._data = typeof data === 'function' // 这行代码解释了平时为啥data为什么支持函数式的
            ? getData(data, vm)
            : data || {};
        ...
        proxy(vm, "_data", key);// 将data绑定到vue的this上
        ...
        observe(data, true);
    }
    

    这里observe(data)会return一个Observe类的实例

    function observe (value, asRootData) {
        if (!isObject(value) || value instanceof VNode) {
            return
        }
        var ob;
        if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
            ob = value.__ob__;
        } else if (
            shouldObserve &&
            !isServerRendering() &&
            (Array.isArray(value) || isPlainObject(value)) &&
            Object.isExtensible(value) &&
            !value._isVue
        ) {
            ob = new Observer(value);
        }
        if (asRootData && ob) {
            ob.vmCount++;
        }
        return ob
    }
    

    Observe类将传进来的参数进行递归调用,最终都会调用this.walk

    var Observer = function Observer (value) {
        this.value = value;
        this.dep = new Dep();
        this.vmCount = 0;
        def(value, '__ob__', this);
        if (Array.isArray(value)) {
            if (hasProto) {
                protoAugment(value, arrayMethods);
            } else {
                copyAugment(value, arrayMethods, arrayKeys);
            }
            this.observeArray(value);
        } else {
            this.walk(value);
        }
    };
    
    Observer.prototype.walk = function walk (obj) {
        var keys = Object.keys(obj);
        for (var i = 0; i < keys.length; i++) {
            defineReactive$$1(obj, keys[i]);
        }
    };
    

    终于看见和definePropoty长得差不多的defineReactive,其实defineReactive就是创建响应式对象,是对definePropoty的一层封装,到这里响应式数据的初始化就算完成了,完整代码如下:

    function defineReactive$$1 (
        obj,
        key,
        val,
        customSetter,
        shallow
    ) {
        var dep = new Dep();
    
        var property = Object.getOwnPropertyDescriptor(obj, key);
        if (property && property.configurable === false) {
            return
        }
    
        var getter = property && property.get;
        var setter = property && property.set;
        if ((!getter || setter) && arguments.length === 2) {
            val = obj[key];
        }
    
        var childOb = !shallow && observe(val);
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: function reactiveGetter () {
                var value = getter ? getter.call(obj) : val;
                if (Dep.target) {
                    dep.depend();
                    if (childOb) {
                        childOb.dep.depend();
                        if (Array.isArray(value)) {
                            dependArray(value);
                        }
                    }
                }
                return value
            },
            set: function reactiveSetter (newVal) {
                var value = getter ? getter.call(obj) : val;
                if (newVal === value || (newVal !== newVal && value !== value)) {
                    return
                }
            
                if (process.env.NODE_ENV !== 'production' && customSetter) {
                    customSetter();
                }
          
                if (getter && !setter) { return }
                if (setter) {
                    setter.call(obj, newVal);
                } else {
                    val = newVal;
                }
                childOb = !shallow && observe(newVal);
                    dep.notify();
                }
          });
    }
    

    第二步:建立依赖

    建立依赖其实就是触发Object.defineProperty中定义get的一个过程,我们都知道get是在获取对象值的时候触发的函数,在vue运行过程中,get的触发是在beforeMount和mounted这两个声明周期之间,这里就不去罗列模板解析过程了,大致就是一个template => AST => render函数 => Vnode => DOM的过程,这里接着最上面created声明周期后的部分进行,执行了$mount

    Vue.prototype._init = function (options) {
        initState(vm);
        initProvide(vm); 
        callHook(vm, 'created');
    
        if (vm.$options.el) {
          vm.$mount(vm.$options.el);
        }
    }
    
    Vue.prototype.$mount = function (
        el,
        hydrating
    ) {
        el = el && inBrowser ? query(el) : undefined;
        return mountComponent(this, el, hydrating)
    };
    
    function mountComponent (
        vm,
        el,
        hydrating
    ) {
        vm.$el = el;
        ...
        callHook(vm, 'beforeMount');
        ...
        // 这里有一段updateComponent的定义
        ...
        new Watcher(vm, updateComponent, noop, {
            before: function before () {
                if (vm._isMounted && !vm._isDestroyed) {
                    callHook(vm, 'beforeUpdate');
                }
            }
        }, true );
        ...
        if (vm.$vnode == null) {
            vm._isMounted = true;
            callHook(vm, 'mounted');
        }
        return vm
    }
    

    从上图可以看出beforeMount和mounted之间其实就定义了一个名为updateComponent(它是Watcher里的一个回调,发生在Watcher的get中),然后new了一个Watcher。
    这里主要讲讲Dep和Watcher,先介绍Watcher和Watcher是如何作为target定义到Dep上的:

    var Watcher = function Watcher (
        vm,
        expOrFn,
        cb,
        options,
        isRenderWatcher
    ) {
        this.vm = vm;
        if (isRenderWatcher) {
            vm._watcher = this;
        }
        vm._watchers.push(this);
      
        if (options) {
            // mounted阶段new的那个Watcher里只有before字段,其他初始化全都是false
            this.deep = !!options.deep;
            this.user = !!options.user;
            this.lazy = !!options.lazy;
            this.sync = !!options.sync;
            this.before = options.before;
        } else {
            this.deep = this.user = this.lazy = this.sync = false;
        }
        this.cb = cb;
        this.id = ++uid$2; // 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 = process.env.NODE_ENV !== 'production'
            ? expOrFn.toString()
            : '';
      
        if (typeof expOrFn === 'function') {
            this.getter = expOrFn;
        } else {
            this.getter = parsePath(expOrFn);
            if (!this.getter) {
                this.getter = noop;
                process.env.NODE_ENV !== 'production' && warn(
                "Failed watching path: \"" + expOrFn + "\" " +
                'Watcher only accepts simple dot-delimited paths. ' +
                'For full control, use a function instead.',
                vm
                );
            }
        }
        this.value = this.lazy
            ? undefined
            : this.get();
    };
    

    上面的最后调用了this.get(),在get()函数里利用pushTarget把Watcher 作为 target 定义在了Dep上,并且执行了this.getter.call(vm, vm);这里的getter是Watcher构造函数的第二个参数expOrFn,内容为vm._update(vm._render(), hydrating),也就是触发了页面的渲染

    function pushTarget (target) {
        targetStack.push(target);
        Dep.target = target;
    }
    
    Watcher.prototype.get = function get () {
        pushTarget(this);
        var value;
        var vm = this.vm;
        try {
            value = this.getter.call(vm, vm);
        } catch (e) {
            if (this.user) {
                handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
            } else {
                throw e
            }
        } finally {
        
            if (this.deep) {
                traverse(value);
            }
            popTarget();
            this.cleanupDeps();
        }
        return value
    };
    

    在vm._update的渲染过程中,因为引用了data中的数据,所以会触发第一阶段中defineProperty为data内数据设置的get函数,代码如下:如果Dep.target存在会调用dep.depend()(Dep.target其实是一个Watcher)

    function defineReactive$$1 (){
        Object.defineProperty(obj, key, {
            ...
            var dep = new Dep();
            ...
            get: function reactiveGetter () {
                var value = getter ? getter.call(obj) : val;
                if (Dep.target) {
                    dep.depend();
                    ...
                }
            }
        }
    }
    

    再看看Dep类,在defineReactive中会new一个Dep的实例,这里subs是一个装watcher的数组,一般在不自定义watch的前提下,这个数组里都只有一个Watcher

    var Dep = function Dep () {
        this.id = uid++;
        this.subs = [];
    };
    
    Dep.prototype.depend = function depend () {
        if (Dep.target) {
            Dep.target.addDep(this); // 根据上面的描述这里Dep.target就是Watcher
        }
    };
    
    Watcher.prototype.addDep = function addDep (dep) {
        var id = dep.id;
        if (!this.newDepIds.has(id)) {
            this.newDepIds.add(id);
            this.newDeps.push(dep);
            if (!this.depIds.has(id)) {
                dep.addSub(this);
            }
        }
    };
    
    Dep.prototype.addSub = function addSub (sub) {
          this.subs.push(sub);
    };
    

    忽视掉和响应式数据无关的部分,到这里基本就是mount结束的地方了,总结下都干了什么,触发beforeMount生命周期,new了一个Watcher对象,渲染模板,触发数据的get初始化,对每个响应式数据的Dep实例进行依赖收集,然后触发Mounted生命周期。

    第三步:派发更新

    当有响应式的数据被改变时,触发set函数,调用dep.notify()

    set: function reactiveSetter (newVal) {
        var value = getter ? getter.call(obj) : val;
        if (newVal === value || (newVal !== newVal && value !== value)) {
            return
        }
        if (process.env.NODE_ENV !== 'production' && customSetter) {
            customSetter();
        }
        if (getter && !setter) { return }
        if (setter) {
            setter.call(obj, newVal);
        } else {
            val = newVal;
        }
        childOb = !shallow && observe(newVal);
    
        dep.notify();
    }
    

    这里subs就是一个装Watcher的数组(在没有绑定自定义Watcher的简单的Vue对象中,这个数组的长度是1),所以就等于是调用了当前vue对象对应Watcher的update()

    Dep.prototype.notify = function notify () {
        var subs = this.subs.slice();
        if (process.env.NODE_ENV !== 'production' && !config.async) {
            subs.sort(function (a, b) { return a.id - b.id; });
        }
        for (var i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
        }
    };
    

    Watcher的update()会对根据Watcher初始化传入的option中sync字段进行一个判断,如果是true的直接触发run(),如果不是会进行一个队列的操作。因为我们在$mount过程中new Watcher时传的option只有before字段,所以其他lazy,sync等字段都是false,所以这里会产生一个队列,用于存放Watcher

    Watcher.prototype.update = function update () {
        if (this.lazy) { //这里是false
            this.dirty = true;
        } else if (this.sync) { // 这里是false
            this.run();
        } else {
            queueWatcher(this);
        }
    };
    

    这个队列会先判断之前是否添加过这个watcher,如果没有则添加,并会有一个针对id的排序插入

    function queueWatcher (watcher) {
        var id = watcher.id;
        if (has[id] == null) {
            has[id] = true;
            if (!flushing) {
                queue.push(watcher);
            } else {
         
                var i = queue.length - 1;
                while (i > index && queue[i].id > watcher.id) {
                    i--;
                }
                queue.splice(i + 1, 0, watcher);
            }
            
            if (!waiting) {
                waiting = true;
    
                if (process.env.NODE_ENV !== 'production' && !config.async) {
                    flushSchedulerQueue();
                    return
                }
                nextTick(flushSchedulerQueue);
            }
        }
    }
    

    flushSchedulerQueue,首先会对队列中的Watcher进行排序,然后触发option中的before,也就是beforeUpdate的生命周期函数,然后执行Watcher.run()

    function flushSchedulerQueue () {
        queue.sort(function (a, b) { return a.id - b.id; });
        ...
        for (index = 0; index < queue.length; index++) {
            watcher = queue[index];
            if (watcher.before) {
                watcher.before();
            }
            /**
                before () {
                    if (vm._isMounted && !vm._isDestroyed) {
                        callHook(vm, 'beforeUpdate');
                    }
                }  
            **/
            id = watcher.id;
            has[id] = null;
            watcher.run();
        }
        ...
        // 下面就不介绍了。。。
        // 队列的备份
        var activatedQueue = activatedChildren.slice();
        var updatedQueue = queue.slice();
        // 队列的初始化
        resetSchedulerState();
        // 触发activated和updated的生命周期函数
        callActivatedHooks(activatedQueue);
        callUpdatedHooks(updatedQueue);
    }
    

    run的时候触发get(),会和首次mount过程类似,多了patch的过程,其中涉及著名的Diff算法,用于渲染页面,从而更新页面,并建立新的依赖关系

    Watcher.prototype.run = function run () {
        if (this.active) {
            var value = this.get();
            if (
                value !== this.value ||
                // Deep watchers and watchers on Object/Arrays should fire even
                // when the value is the same, because the value may
                // have mutated.
                isObject(value) ||
                this.deep
            ) {
                // set new value
                var oldValue = this.value;
                this.value = value;
                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);
                }
            }
        }
    };
    

    完~

    大致流程就是这样了,似乎写的有点乱,如有问题欢迎大佬们指正

    相关文章

      网友评论

          本文标题:Vue响应式原理(Object.defineProperty)全

          本文链接:https://www.haomeiwen.com/subject/vzqdmqtx.html