数据响应式解决
通过查看源码解决问题
vm.msg = {count:0}
,从新给属性 msg 赋值,是否是响应式的?(是)vm.arr[0] = 4
,给数组元素赋值,视图是否会更新?(不是)vm.arr.length = 0
,批改数组的 length,视图是否会更新?(不是)vm.arr.push(4)
,视图是否会更新?(是)
响应式解决的入口
- 响应式解决入口 在 initState 中
- initState 办法在 src/core/instance/state.js 中实现
-
办法中重要办法为 observer
... // 参数:options 中的 data 参数 2 示意根数据(根数据额定解决)observe(data,true /* asRootData */) ...
解析 observe 办法
- observe 办法定义在 src/core/observer/index.js
- 如果以后 data 有 observer 则间接返回,没有给属性创立一个 observer,并返回
- 对 data 对象进行判断,如果有
__ob__
属性,返回 observer。没有,则 new Observer(data)(外部把它变成响应式,注册 getter/setter),再返回 - 在 observe 中的 getter 会收集依赖,setter 会派发更新(首次编译会触发 getter)
-
Dep.target 在 watcher 类中定义,参考 mountComponent 办法,办法中会 new Watcher
export function observe (value: any, asRootData: ?boolean): Observer | void { // 判断 value 是否是对象或 VNode 实例 if (!isObject(value) || value instanceof VNode) {return} let ob: Observer | void // 如果 value 有 __ob__(Observer 类的属性)完结 // 做过响应式就不必再操作了,相当于加了个缓存 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 ) { // 创立一个 observer 对象,ob = new Observer(value) } // 根数据 data,会额定 ++ 标记 if (asRootData && ob) {ob.vmCount++} // 返回一个 Obserrver 类 return ob }
解析 Observer 类
- 外部会给 value 数据定义
__ob__
属性,值为 Observer,且不可枚举。 - 初始化 Dep 实例,每个 Observer 对应一个 Dep 实例
-
未来遍历 value,给它外部所有属性设置 getter,setter 时不思考 此属性。
// this 指 Observer def(value, '__ob__', this)
-
判断 value 数据类型。如果数组,做数组响应式解决。对象则调用 walk 办法遍历对象属性增加 getter,setter,并依赖注入和派发更新。
if (Array.isArray(value)) { // 以后浏览器是否反对对象原型 __proto__ // 修补数组中的原型办法,如果数组发生变化调用 notify 更新视图 if (hasProto) {protoAugment(value, arrayMethods) } else {copyAugment(value, arrayMethods, arrayKeys) } // 遍历数组所有元素,是对象的元素会转化为响应式对象,而非所有属性!!!!// 下标和 length 是属性,无奈通过 vm.msg[0] 更新,因为性能问题 // 能够应用 vm.msg.splice(0,1,100) 代替 this.observeArray(value) } else { // 遍历对象中的每个属性,转换成 setter/getter this.walk(value) }
defineReactive 办法解析
- walk 办法外部会调用 用来给对象属性做响应式解决的
- 办法中的 shallow 参数,true 示意浅层响应,非浅层须要递归设置属性响应式
- 给对象属性创立一个 Dep 实例,用于在 get 办法中收集依赖,set 办法中派发更新
- 在对象属性劫持的 get,和 set 中,会判断用户是否给属性设置了 getter/setter, 如果设置了,也会调用。
set 依赖收集解析
-
Dep.target 动态对象 存在则把 Watcher 增加到 dep 的 subs 数组中
get: function reactiveGetter () {const value = getter ? getter.call(obj) : val // 如果存在以后依赖指标,即 watcher 对象,则建设依赖 if (Dep.target) { // dep 是为以后属性收集依赖的对象。收集以后的 watcher 对象到以后属性的 dep 的 subs 数组中 dep.depend() // 给以后 属性对应的值 收集依赖,如果值是对象,则调用值对应 Observer 的 dep // 这里的 dep 是为递归的值 observer 收集依赖,与下面的 dep 不同 // 例如 data:{msg:{name:"zhangsan"} }, 这里给 name 增加依赖 if (childOb) {childOb.dep.depend() // 如果属性值是数组,则非凡解决收集数组对象依赖 例如 data:{arr:[1,2,3]} 此办法解决 [1,2,3] // 外层解决 arr if (Array.isArray(value)) {dependArray(value) } } } return value }
let childOb = !shallow && observe(val)
Dep.target 是哪里赋值的?
- 在首次渲染时候,new Watcher 外部赋值的,外部的 pushTarget 赋值的
- Dep.target 全局惟一,一个组件对应一个 watcher,一个 watcher 对应一个 Dep.target
- 一个组件挂载时会创立一个 watcher,同时赋值给 Dep.target
-
push 到栈是因为,如果有组件嵌套,(则 watcher 嵌套,先把父 watcher 放入栈中)执行子 watcher,执行实现从栈中弹出,执行父组件渲染
Dep.target = null const targetStack = [] export function pushTarget (target: ?Watcher) { // 为什么要入栈?// 每个组件对应 watcher 对象(每个组件有个 mountComponent)// 如果 a 组件嵌套 b 组件,渲染按时发现有子组件,就先渲染 b 子组件,a 组件的渲染过程先被被挂载起来 // a 组件对应的 watcher 对象被存储在栈中,(栈特点先进后出)// 当 b 子组件渲染实现会从栈中弹出,而后执行父组件渲染 targetStack.push(target) Dep.target = target } export function popTarget () { // 出栈操作 targetStack.pop() Dep.target = targetStack[targetStack.length - 1] }
这里赋值了,哪里触发的数据劫持的 get 办法呢?
-
首次渲染在给 Dep.target 赋值过后会调用 updateComponent 办法,生成虚构 dom 更新视图
updateComponent = () => { // _render 或获取虚构 dom _update 将虚构 dom 转为实在 dom vm._update(vm._render(), hydrating) }
- 在 _render 办法中,会触发 数据劫持的 get 办法,能够调试一下
- 渲染相干办法:_c 办法是 createElement 办法 h 函数,_s 办法是 toString(),_v 办法是创立文本虚构节点 createTextVNode
- 如图拜访 msg 属性会触发数据劫持中的 get 办法,从而在 get 办法内收集依赖, 收集 msg 属性的 Dep 到以后组件的 watcher 内定义的 dep 的 subs 数组中,如果收集过则不反复收集(就是 data 中的 msg 在视图中应用两次,只会收集一次依赖)
- 因而,get 中首次依赖收集,是在首次编译时触发的!