每一次实例化一个组件,都会调用 initData 而后调用 observe 方法,observe 方法调用了 new Observer(value), 而且返回 __ob__
。javascript
在 new Observer 中作了两件事:vue
__ob__
属性上,这个实例在后面有用处。若是是对象:java
横向遍历对象属性,调用 defineReactive;react
递归调用 observe 方法, 当属性值不是数组或者对象中止递归es6
下面对 defineReactive 方法作了详细的注释:vue-router
export function defineReactive( obj: Object, key: string, val: any, customSetter ? : ? Function, shallow ? : boolean ) { const dep = new Dep(); // 闭包建立依赖对象; 每一个对象的属性都有本身的dep // 下面是针对已经经过Object.defineProperty 或者Object.seal Object.freeze 处理过的数据 const property = Object.getOwnPropertyDescriptor(obj, key); // 若是configurable为false ,再次Object.defineProperty(obj, key)会报错,而且不会成功;因此直接返回 // 因此能够针对性的使用Object.freeze/seal优化性能。 if (property && property.configurable === false) { return; } const getter = property && property.get; const setter = property && property.set; // 正常状况下 咱们使用的数据getter、setter都是不存在的,而且在new Observer()中调用defineReactive的参数只有两个 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; // 也就是说 这行代码通常状况下会执行 } // 通常状况下 shallow是false ;childOb就是返回的Observer实例,这个实例是存储在数据的__ob__属性上的 // let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; // getter 不存在 ,直接用val if (Dep.target) { // Dep.target是一个全局数据,保存的是watcher栈(targetStack)栈顶的watcher, dep.depend(); // 闭包dep把当前watcher收集起来; 收集依赖真正发生在render方法执行的时候(也就是虚拟dom生成的时候) if (childOb) { // val不是对象(非Array 或者object) observe方法才会返回一个Observer实例,不然返回undefined // 此处为何要执行childOb.dep.depend()呢? // 这么作的效果是:在对象上挂载的__ob__的dep对象把点前watcher添加到了依赖里,这个dep和闭包dep不是一个。 // 目的在于: // 1.针对对象:要想this.$set/$del时候可以触发组件从新渲染,须要把渲染watcher保存下来,而后在$set中调用 ob.dep.notify();这里就用到了__ob__属性 // 2.针对数组:数组的拦截中(调用splice push 等法法)要想触发从新渲染,调用 ob.dep.notify() 这里就用到了__ob__属性 childOb.dep.depend(); if (Array.isArray(value)) { // 若是value是一个数组,在observe方法中走的是数组那套程序,这些元素没有被Object.defineProperty这一系列的处理(元素当作val处理),即使元素是object/array ,没有childOb.dep.depend()这样的一个过程,致使上面this.$set/$del、数组没法触发从新渲染; // 因此调用dependArray 针对数组作处理 这里就用到了__ob__属性 dependArray(value); } } } return value; }, set: function reactiveSetter(newVal) { // 通常没有getter const value = getter ? getter.call(obj) : val; // 值未变化, newVal !== newVal && value !== value 应该针对的是NaN if (newVal === value || (newVal !== newVal && value !== value)) { return; } if (process.env.NODE_ENV !== "production" && customSetter) { customSetter(); } // getter 和setter 要成对才行 if (getter && !setter) return; if (setter) { setter.call(obj, newVal); } else { val = newVal; } // 从新设置值以后,须要从新observe ,而且更新闭包变量 childOB childOb = !shallow && observe(newVal); // 更新 dep.notify(); }, }); }
若是是数组:vuex
修改数组的
__proto__
属性值,指向一个新的对象;express
function protoAugment (target, src: Object) { target.__proto__ = src }
这个新对象中从新定义以下方法:数组
'push','pop','shift','unshift','splice','sort','reverse'
同时这个对象的 __proto__
指向 Array.prototype。闭包
const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto);
最后项目的代码在控制台打印出下面的截图
data() { return { data1: [{ name: 1 }] } },
同时对数组中的每一个元素作 observe 递归处理。
watch 的使用方法通常以下:
watch: { a: function(newVal, oldVal) { console.log(newVal, oldVal); }, b: 'someMethod', c: { handler: function(val, oldVal) { /* ... */ }, deep: true }, d: { handler: 'someMethod', immediate: true }, e: [ 'handle1', function handle2(val, oldVal) {}, { handler: function handle3(val, oldVal) {}, } ], 'e.f': function(val, oldVal) { /* ... */ } }
watch 的处理按照以下流程, 把其中的关键代码罗列出来了:
-- > initData() // 初始化组件的时候调用 若是组件中有watch选项,调用initWatch -- > initWatch() if (Array.isArray(handler)) { // 这里处理数组的状况,也就是上面e的状况 for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } -- > createWatcher() if (isPlainObject(handler)) { // 兼容c(对象) options = handler handler = handler.handler } // 若是是b 字符串的状况,须要在vm上有对应的数据 if (typeof handler === 'string') { handler = vm[handler] } // 默认是 a(函数) vm.$watch(expOrFn, handler, options) -- > vm.$watch() options.user = true // 添加参数 options.user = true ,处理immediate:true的状况 const watcher = new Watcher(vm, expOrFn, cb, options) -- > new Watcher() // 建立watcher this.getter = parsePath(expOrFn) // 这个getter方法主要是get一下watch的变量,在get的过程当中触发依赖收集,把当前watcher添加到依赖 this.value = this.lazy // 选项lazy是false ? undefined : this.get() // 在constructor中直接调用get方法 -- > watcher.get() pushTarget(this) // 把当前watcher推入栈顶 value = this.getter.call(vm, vm) // 这时候这个watch的变量的依赖里就有了当前watcher -- > watcher.getter() // 依赖收集的地方
当 watch 的变量变化的时候,会执行 watcher 的 run 方法:
run() { if (this.active) { const value = this.get() // 渲染watcher状况下 value是undefined // 在自定义watcher的状况下 value就是监听的值 if ( value !== this.value || // 当watch的值有变化的时候 isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value // 自定义watcher的user是true ,cb就是那个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) } } } }
上面的的代码中 value !== this.value 和 deep 比较好理解,数值变化触发 handler;
可是 isObject(value)对应的什么状况呢?看一下下面的例子就知道了:
data() { return { data1: [{ name: 1 }], } }, computed: { data2() { let value = this.data1[0].name // return this.data1 // 返回的是一个数组,因此data2一致是不变的 } }, watch: { data2: function() { // 虽然data2的值一直是data1,没有变化;可是由于data2知足isObject,因此仍然能触发handler // 由此能够想到,能够在computed中主动去获取某个数据属性来触发watch,而且避免在watch中使用deep // 可是这样也不太合适,由于能够直接使用'e.f'这种例子来代替; // 因此根据要实际状况肯定 console.log('data2'); } } created() { setInterval(() => { this.data1[0].name++ }, 2000) }
computed 首先是建立 watcher,与渲染 watcher、自定义 watcher 不一样之处:初始化的时候不会执行 get 方法,也就是不会作依赖收集。
另外使用 Object.defineProperty 定义 get 方法:
function createComputedGetter(key) { return function computedGetter() { const watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { // lazy=true 而后 dirty 也是true if (watcher.dirty) { watcher.evaluate(); // 把computed watcher添加到涉及到的全部的变量的依赖中; } if (Dep.target) { watcher.depend(); // 主动调用depend方法;假如这个computed是用在页面渲染上,就会把渲染watcher添加到变量的依赖中 } return watcher.value; } }; }
当 computed 数据在初次渲染中:
-- > render // 渲染 -- > computedGetter // computed Object.defineProperty 定义get方法: -- > watcher.evaluate() // 计算获得watcher.value -- > watcher.get() -- > pushTarget(this) // 把当前computed watcher 推入watcher栈顶 -- > watcher.getter() // getter方法就是组件中computed定义的方法,执行的时候会作依赖收集 -- > dep.depend() // 把当前computed watcher加入变量的依赖中 -- > popTarget() // 把当前 computed watcher 移除栈,通常来讲渲染watcher会被推出到栈顶 -- > cleanupDeps() // 清除多余的watcher 和 dep -- > watcher.depend() // 这是computed比较特殊的地方。假如computed中依赖变量data中的数据,这个步骤把当前watcher添加到变量的依赖中;为何要这么作呢?我的猜想意图是computed的目的是作一个处理数据的桥梁,真正的响应式仍是须要落实到data中的数据。
当 computed 中的依赖数据变化的时候会走以下流程:
-- > watcher.update() // 这是个 computed watcher,其中lazy为true,因此不会往下走 if (this.lazy) { this.dirty = true } -- > watcher.update() // 渲染watcher render 以后的过程就如同初次渲染同样
渲染 watcher 相对好理解一些
new Watcher(渲染 watcher) ->watcher.get-> pushTarget(this) ->watcher.getter()-> render -> Object.defineProperty(get) -dep.depend()-> popTarget()->watcher.cleanupDeps()
watcher.getter 是下面方法:
updateComponent = () => { vm._update(vm._render(), hydrating) }
源码在 vue/src/core/observer/watcher.js 中;
须要注意到 vue 中有一套清除 watcher 和 dep 的方案;vue 中的依赖收集并非一次性的,从新 render 会触发新一次的依赖收集,这时候会把无效的 watcher 和 dep 去除掉,这样可以避免无效的更新。
以下 computed ,只要有一次 temp<=0.5
, 改变 b 都再也不会在打印 temp
;缘由在于当 temp<0.5
以后, this.b
不会把当前 a
放进本身的 dep 中,也就不会再触发这个 computed watcher 了
data() { return { b: 1 } }, computed: { a() { var temp = Math.random() console.log(temp); // 只要有一次a<=0.5 接下来就不会打印temp了 if (temp > 0.5) { return this.b } else { return 1 } } }, created() { setTimeout(() => { this.b++ }, 5000) },
这里面主要是 watcher.js 中的 cleanupDeps 方法在处理;
cleanupDeps() { let i = this.deps.length // 遍历上次保存的deps while (i--) { // i-- const dep = this.deps[i] // newDepIds 是在本次依赖收集中加入的新depId集合 // 把不在newDepIds中的dep清除 if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } // depIds是一个es6 set集合 ,是引用类数据 // newDepIds相似于一个临时保存的地方,最终须要把数据保存到depIds。左手到右手的把戏 // newDeps 和 newDepIds 是同样的 let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 }
依赖于 vue 自身的响应式原理,经过构建一个 Vue 实例,在 render 过程当中完成依赖的收集。
store._vm = new Vue({ data: { $$state: state, // 自定义的state数据 }, computed, });
Vue.mixin({ beforeCreate() { if (isDef(this.$options.router)) { this._routerRoot = this; this._router = this.$options.router; this._router.init(this); // 关键是这行代码,把_route属性进行响应式处理 Vue.util.defineReactive( this, "_route", this._router.history.current ); } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; } registerInstance(this, this); }, destroyed() { registerInstance(this); }, }); Object.defineProperty(Vue.prototype, "$route", { get() { return this._routerRoot._route; }, });
渲染 router-view 的时候会触发上面的
// 该组件渲染的时候render方法 render() { ... // 当调用$route的时候会触发依赖收集 var route = parent.$route; }