首页 > 代码库 > .4-Vue源码之数据双绑(2)

.4-Vue源码之数据双绑(2)

开播了开播了!

 

  vue通过数据劫持来达到监听和操作DOM更新,上一节简述了数组变化是如何监听的,这一节先讲讲对象属性是如何劫持的。

    // Line-855
    Observer.prototype.walk = function walk(obj) {
        var keys = Object.keys(obj);
        for (var i = 0; i < keys.length; i++) {
            // Go!
            defineReactive$$1(obj, keys[i], obj[keys[i]]);
        }
    };

  上一节说到这里,现在进入defineReactive$$1来看看具体的劫持过程,函数比较长。

  函数接受4个参数,分别为数据对象、键、值、默认设置,由于这里只传了3个,先不管第4个

    // Line-925
    function defineReactive$$1(
        obj,
        key,
        val,
        customSetter
    ) {
        // 生成一个依赖管理
        var dep = new Dep();
        // getOwnPropertyDescriptor方法以对象形式返回键描述信息
        // 包括enumerable、writable等等
        var property = Object.getOwnPropertyDescriptor(obj, key);
        // 不可修改的键直接返回
        if (property && property.configurable === false) {
            return
        }

        // 获取对应的getter与setter
        var getter = property && property.get;
        var setter = property && property.set;
        // 值为对象时的嵌套监听 
        // 当前值为‘Hello Vue‘的字符串 直接返回
        var childOb = observe(val);
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: function reactiveGetter() {
                // 有getter方法直接调用
                var value = http://www.mamicode.com/getter ? getter.call(obj) : val;
                // 这里暂时为null
                if (Dep.target) {
                    dep.depend();
                    if (childOb) {
                        childOb.dep.depend();
                    }
                    if (Array.isArray(value)) {
                        dependArray(value);
                    }
                }
                // 直接返回值
                return value
            },
            set: function reactiveSetter(newVal) {
                // 获取当前值
                var value = http://www.mamicode.com/getter ? getter.call(obj) : val;
                // 这是比较个啥 只有NaN才会不等于自身吧
                if (newVal === value || (newVal !== newVal && value !== value)) {
                    return
                }
                // 这个参数没传 暂时不知道干啥的
                if ("development" !== ‘production‘ && customSetter) {
                    customSetter();
                }
                // 调用默认setter方法或将新值赋给当前值
                if (setter) {
                    setter.call(obj, newVal);
                } else {
                    val = newVal;
                }
                // 
                childOb = observe(newVal);
                // 广播变化
                dep.notify();
            }
        });
    }

  这里很多方法跳不进去,所以没法调试看效果,等这个搞完,做数据变化调试的时候再来详细解释,目前将就看一下。

  

  由于value只有一个message键,所以一次就结束了,函数疯狂返回,然后回到了observe的构造函数:

    // Line-900
    function observe(value, asRootData) {
        if (!isObject(value)) {
            return
        }
        var ob;
        if (hasOwn(value, ‘__ob__‘) && value.__ob__ instanceof Observer) {
            ob = value.__ob__;
        } else if (
            observerState.shouldConvert &&
            !isServerRendering() &&
            (Array.isArray(value) || isPlainObject(value)) &&
            Object.isExtensible(value) &&
            !value._isVue
        ) {
            // 跑完这里
            ob = new Observer(value);
        }
        if (asRootData && ob) {
            ob.vmCount++;
        }
        return ob
    }

  

  来看看搞了这么久,ob是个什么东西:

  技术分享

  dep是依赖收集数据,包含计数id和依赖数组subs,还有4个原型方法。

  value是被监听数据,除了数据本身,还添加了__ob__属性引用自身,自定义了get和set方法,计数的vmCount。

  原型方法包含一个遍历数组数据的observeArray与监听对象的walk方法。

 

  接下来的代码将vmCount加1,然后返回这个ob对象,返回到了initData函数:

    // Line-3012
    function initData(vm) {
        // ..格式化、代理
        // ...
        // 监听数据
        observe(data, true /* asRootData */ );
    }

  这个函数也到头了,返回到了initState函数:

    // Line-2948
    function initState(vm) {
        // ...
        // 从这里跳了出来
        if (opts.data) {
            initData(vm);
        } else {
            observe(vm._data = {}, true /* asRootData */ );
        }
        // 剩下的两个参数没有
        if (opts.computed) {
            initComputed(vm, opts.computed);
        }
        if (opts.watch) {
            initWatch(vm, opts.watch);
        }
    }

  好吧,这个也没啥执行的,返回到了最原始的_init初始化函数:

    // Line-3924
    Vue.prototype._init = function(options) {
        // ...各种初始化
        // ... 
        // 从这里跳出来
        initState(vm);
        initProvide(vm); // resolve provide after data/props
        callHook(vm, ‘created‘);

        /* istanbul ignore if */
        if ("development" !== ‘production‘ && config.performance && mark) {
            vm._name = formatComponentName(vm, false);
            mark(endTag);
            measure(((vm._name) + " init"), startTag, endTag);
        }

        if (vm.$options.el) {
            vm.$mount(vm.$options.el);
        }
    };

   接下来是initProvide函数,provide翻译成中文是准备的意思。

    // Line-3924
    function initProvide(vm) {
        // 没有这个属性 跳
        var provide = vm.$options.provide;
        if (provide) {
            vm._provided = typeof provide === ‘function‘ ?
                provide.call(vm) :
                provide;
        }
    }

  然后调用钩子函数,进入created阶段,由于没有定义执行内容,所以直接跳出来,代码就不贴了。

 

  那个dev内容也不太清楚记录的什么,暂时先不管,下面是双绑的另一个大模块:AST。

  本节先简单结束了,数据劫持也差不多这些,下一节开始跑节点挂载。

 

  上图:

技术分享

.4-Vue源码之数据双绑(2)