.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 = 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 = 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。
本节先简单结束了,数据劫持也差不多这些,下一节开始跑节点挂载。
上图:
老子要日穿V8引擎