Vue中的响应式处理(源码解读)

响应式处理入口

Observer函数定义在src/core/observer/index.js中,首先,我们来看一下这个函数的源码:

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 判断value是否是对象  
  if (!isObject(value) || value instanceof VNode) {
    // 如果不是一个对象或者是vNode的一个实例,则直接返回,不需要做响应式处理
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    // 如果value有__ob__(observer对象)属性,则令ob为value.__ob__,并返回ob
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 创建一个Observer对象
    ob = new Observer(value)
  }
  // 如果是根数据
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}
  • 该函数会返回一个Observer的实例,参数有两个,一个为value(在初始化时传入的就是在Vue组件中定义的data),一个为asRootData(来判断当前data是否为根数据)
  • 如果当前传入的value不是一个对象或者是一个vNode的实例,则直接返回不需要进行响应式处理
  • 如果value__ob__observer对象)属性,即之前做过响应式的处理,则令obvalue.__ob__,并返回ob
  • 判断value是否可以进行响应式处理,如果可以创建一个Observer对象

Observer类

  • 类文件目录:src/core/observer/index.js
  • 上面有说过observe函数,会返回一个Observer实例,那么接下来看一下Observer类的定义
  • 将实例挂载到观察对象的__ob__属性上,且该属性是不可以枚举的
  • 对数组进行响应式处理
  • 通过walk方法,对对象进行响应式处理,将目标对象转换为settter/gettter
  • getter/setterd的作用是用来收集依赖于派发更新
export class Observer {
  value: any;    // 观测对象
  dep: Dep;    // 依赖对象
  vmCount: number; // 实例计数器 number of vms that have this object as root $data
  
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0   // 初始化实例的vmCount为0
    def(value, '__ob__', this)     // 将实例挂载到观察对象的__ob__属性,并且__ob__属性是不可枚举的
    // 数组的响应式处理
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 为数组中的每一个对象创建一个observer对象
      this.observeArray(value)
    } else {
      // walk会遍历对象中的每一个属性,转换成setter/getter
      this.walk(value)
    }
  }

defineReactive(响应式处理)

  • 函数文件目录:src/core/observer/index.js
  • 上面提到过通过walk方法,可以对象进行响应式处理,那么在walk方法中,则是调用的defineReactivedefineReactive方法则是为一个对象定义响应式属性,下面具体来看一下这个方法的实现:
  • 1、判断当前是否存在且是否可配置,如果当前属性存在,且不可配置,那么直接返回
  • 2、获取提供的预定义的存取器函数
  • 3、判断是否递归观察子对象,并将子对象属性转换为getter/setter
  • 4、get:如果预定义的getter存在,则value等于getter调用的返回值,否则直接赋予属性值,同时在get中也会进行依赖收集
  • 5、set
    • 首先会先获取旧值,如果旧值与新值相等或者两个值都为NaN则返回;
    • 如果有getter没有setter则返回;
    • 如果有setter,则执行setter
    • 如果没有getter且没有setter,则令val = newVal
    • 如果新值是对象,则会将新值再转为为getter/setter
    • 最后在set中还会进行派发更新
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean   // shallow是否需要深度监听这个对象
) {
  const dep = new Dep()  // 创建依赖对象实例
  // 获取obj的属性描述符对象
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    // 如果当前属性为不可配置,那么直接返回
    return
  }

  // 提供预定义的存取器函数
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 判断是否递归观察子对象,并将子对象属性转换为getter/setter,返回子观察对象 
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 如果预定义的getter存在,则value等于getter调用的返回值
      // 否则直接赋予属性值
      const value = getter ? getter.call(obj) : val
      // 如果存在当前依赖目标,即watcher对象,则建立依赖
      if (Dep.target) {
        dep.depend()
        // 如果子观察目标存在,建立子对象的依赖关系
        if (childOb) {
          childOb.dep.depend()
          // 如果属性是数组,则特殊处理收集数组对象依赖
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      // 返回属性值
      return value
    },
    set: function reactiveSetter (newVal) {
      // 如果预定义的getter存在,则value等于getter调用的返回值
      // 否则直接赋予属性值
      const value = getter ? getter.call(obj) : val
      // 如果新值等于旧值,或者新值旧值为NaN,则不执行
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      // 如果没有setter,则直接返回
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 如果新值是对象,观察子对象并返回子的observer对象
      childOb = !shallow && observe(newVal)
      // 派发更新(发布更改通知)
      dep.notify()
    }
  })
}

依赖收集

  • 上面有提到过在defineReactive中的get里会进行依赖收集,下面具体分析一下依赖收集的过程

  • 可以看到只有当Dep.targettrue的时候,才会进行依赖收集,那么就先从Dep.target入手,

  • 首先我们先找到mountComponent函数,在执行mountComponent函数时,会创建一个Watcher实例,下面是Watcher类中的get方法

    // src/core/oserver/watcher.js
    get () {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
          // 初始化时,getter就是updateComponent函数
          value = this.getter.call(vm, vm)  
        } catch (e) {
          if (this.user) {
            handleError(e, vm, `getter for watcher "${this.expression}"`)
          } else {
            throw e
          }
        } finally {
          // "touch" every property so they are all tracked as
          // dependencies for deep watching
          if (this.deep) {
            traverse(value)
          }
          popTarget()
          this.cleanupDeps()
        }
        return value
      }
    
  • 1)找到src/core/oserver/watcher.js看一下实例的创建,在创建实例时,会执行Watcher类中get方法,get方法中会调用pushTarget方法,pushTarget方法,会使得Dep.target = target,而这个target就是Watcher实例,此时Dep.target就是Watcher实例

  • 2)在执行完pushTarget后,就会执行this.getterthis.getter就是updateComponent函数,该函数会生成虚拟DOM,在进行取值的时候,就会触发属性的get方法,继而执行get中的依赖收集,下面是get进行依赖收集的代码

    	if (Dep.target) {
    	  dep.depend()
    	  // 如果子观察目标存在,建立子对象的依赖关系
    	  if (childOb) {
    	    childOb.dep.depend()
    	    // 如果属性是数组,则特殊处理收集数组对象依赖
    	    if (Array.isArray(value)) {
    	      dependArray(value)
    	    }
    	  }
    	}
    
  • 3)上面由于Dep.targettrue,那么就会执行dep.depend(),这里的dep就是在执行defineReactive函数时,会new一个Dep的实例,而这个Dep就是依赖收集的容器,它会记录那些Watcher依赖自己的变化

  • 4)在文件src/core/oserver/dep.js中找到Dep类的定义,下面可以看到在depend函数的定义,在dep函数中,会通过调用Watcher实例中的addDep方法将自己添加观察者的依赖中

     // src/core/oserver/dep.js
     depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
    
      // src/core/oserver/watcher.js
      addDep (dep: Dep) {
        const id = dep.id
        if (!this.newDepIds.has(id)) {
          this.newDepIds.add(id)
          this.newDeps.push(dep)
          if (!this.depIds.has(id)) {
            dep.addSub(this)
          }
        }
      }
    
  • 5)最后,在addDep函数中,会通过调用Dep实例上的addSub方法,将当前的Watcher实例收集到dep中的subs中,以完成依赖收集

  • 整个收集流程大概如下:
    在这里插入图片描述

数组的响应式处理过程

  • Observer类中,对于对象形式,通过调用walk方法对对象添加了set/get,而对于数组的则是通过重写数组的方法(这些方法是会使数组的结构发生变化)来进行响应式的实现

  • 首先会先判断当前属性中是否有__proto__,如果有,则调用protoAugment方法,否则调用copyAugment

  // src\core\observer\index.js
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    // 初始化实例的vmCount为0
    this.vmCount = 0
    // 将实例挂载到观察对象的__ob__属性
    def(value, '__ob__', this)
    // 数组的响应式处理
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 为数组中的每一个对象创建一个observer对象
      this.observeArray(value)
    } else {
      // 遍历对象中的每一个属性,转换成setter/getter
      this.walk(value)
    }
  }
  • 下面是protoAugment方法的实现,该方法就是将src放在target__proto__上,那么target是传入的属性值,那接下来具体看一下传入的arrayMethods是什么
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
  • 下面是arrayMethods中的代码
  • 1)使用数组的原型创建一个新的对象
  • 2)将会改变数组方法的方法名放在一个数组中methodsToPatch
  • 3)遍历方法名,执行数组的原始方法
  • 4)对插入的新元素,重新遍历数组元素设置为响应式数据
  • 5)调用数组的ob对象发送通知
// src\core\observer\array.js
import { def } from '../util/index'

const arrayProto = Array.prototype
// 使用数组的原型创建一个新的对象
export const arrayMethods = Object.create(arrayProto)
// 修改数组元素的方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  // 保存数组原始方法
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    // 执行数组的原始方法
    const result = original.apply(this, args)
    // 获取数组对象的ob对象
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 对插入的新元素,重新遍历数组元素设置为响应式数据
    if (inserted) ob.observeArray(inserted)
    // notify change
    // 调用了修改数组的方法,调用数组的ob对象发送通知
    ob.dep.notify()
    return result
  })
})
  • 对于没有__proto__的会执行copyAugment(value, arrayMethods, arrayKeys),其中arrayKeys是数组原型上的属性名
  • 该方法会循环数组上的属性名,并通过def重写数组上的方法
// src\core\observer\index.js
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

派发更新dep.notify

  • Watcher分为三种:Computed Watcher,用户Watcher(侦听器),渲染Watcher
  • 上面已经说明了对象与数组的响应式处理过程,下面来具体看一下当在vue中更改一个数据时,是如何进行派发更新的
  • 1)在进行set时,会通过dep.notify()派发更新
  • 2)在notify中,找到每一个watcher对象,调用watcher对象中的update方法进行更新
	// src\core\observer\dep.js
	for (let i = 0, l = subs.length; i < l; i++) {
	   subs[i].update()
	}
  • 3)在update中,由于lazyasync此时为false,所以执行queueWatcher
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
  • 4)在queueWatcher中,会通过has对象保证每个watcher只添加一次;之后会对flushing进行判断,如果flushingfalse,则将当前watcher放于queue队列最后,否则根据idwatcher插入到队列中;最后最后通过 wating 保证对 nextTick(flushSchedulerQueue) 的调用逻辑只有一次
export function queueWatcher (watcher: Watcher) {
  // 获取watcher的id属性
  const id = watcher.id
  // 如果当前id为null的话,说明当前watcher还没有被处理,这里是为了防止watcher被重复处理
  if (has[id] == null) {
    has[id] = true
    // flushing 是否刷新
    // flushing为true,说明queue队列正在被处理,也就是说明watcher对象正在被处理
    // flushing为false,则直接将watcher放在queue队列的最后
    // 将当前要处理的watcher放在队列中
    if (!flushing) {
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}
  • 5)在flushSchedulerQueue中,首先会将flushing置为true,之后会对queue队列中根据id进行排列,这里排列的目的有以下三点:
    • a. 组件被更新顺序是从父组件到子组件,因为先创建父组件,再创建子组件
    • b.组件的用户watcher要在它对应的渲染watcher之前运行(因为用户watcher是在渲染watcher之前创建的)
    • c. 如果一个组件在父组件的 watcher 执行期间被销毁,那么它对应的 watcher 执行都可以被跳过,所以父组件的 watcher 应该先执行
  • 之后就是对queue队列的遍历,可以发现,在遍历的时候,每次都会对queue.length进行求值,因为在 watcher.run() 的时候,很可能用户会再次添加新的 watcher,这样会再次执行到 queueWatcher
  • 最后通过resetSchedulerState把这些控制流程状态的一些变量恢复到初始值,把 watcher 队列清空
// src\core\observer\scheduler.js
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) { // 触发beforeUpdate钩子函数
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }
  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}
  • 6) 最后就是执行watcher上的run方法了,先通过 this.get() 得到它当前的值,然后做判断,如果满足新旧值不等、新值是对象类型、deep 模式任何一个条件,则执行 watcher 的回调;
  • 注意回调函数执行的时候会把第一个和第二个参数传入新值 value 和旧值 oldValue,这就是当我们添加自定义 watcher 的时候能在回调函数的参数中拿到新旧值的原因。
  • 那么对于渲染 watcher 而言,它在执行 this.get() 方法求值的时候,会执行 getter 方法,也就是updateComponent来实现组件的更新
  // src\core\observer\watcher.js
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        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)
        }
      }
    }
  }

总结

最后以一张图来总结一下上面的原理
在这里插入图片描述

文章参考:
Vue源码阅读 - 依赖收集原理
Vue源码系列19------响应式原理----派发更新

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值