乐趣区

关于vue.js:Vue对对象响应式原理分析

数据响应式解决

通过查看源码解决问题

  • 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 中首次依赖收集,是在首次编译时触发的!

退出移动版