数据响应式处理
通过查看源码解决问题
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中首次依赖收集,是在首次编译时触发的!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。