数据响应式解决

通过查看源码解决问题

  • 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 指 Observerdef(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/getterthis.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 = nullconst 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转为实在domvm._update(vm._render(), hydrating)}
  • 在 _render办法中,会触发 数据劫持的 get 办法,能够调试一下
  • 渲染相干办法:_c 办法是createElement办法h函数,_s办法是toString(),_v办法是创立文本虚构节点createTextVNode
  • 如图拜访 msg 属性会触发数据劫持中的 get办法,从而在 get 办法内收集依赖,收集 msg 属性的Dep到以后组件的watcher 内定义的 dep 的 subs 数组中,如果收集过则不反复收集 (就是 data中的msg在视图中应用两次,只会收集一次依赖)
  • 因而, get中首次依赖收集,是在首次编译时触发的!