【Vue源码】第十六节深入响应式原理之依赖收集

学习Object.defineProperty

Object.defineProperty(obj, prop, descriptor)
  • obj:必需。目标对象;
  • prop: 必需。需定义或修改的属性的名字;
  • descriptor:必需。目标属性所拥有的特性

descriptor可以赋值以下内容:

  • value:属性对应的值,可以使任意类型的值,默认为undefined;
  • writable:属性的值是否可以被重写。设置为true可以被重写;设置为false,不能被重写。默认为false;
  • enumerable:此属性是否可以被枚举(使用for…in或Object.keys()),默认为false;
  • configurable:是否可以删除目标属性、是否可以再次修改属性的特性(writable, configurable, enumerable),默认为false;
  • getter:当使用了getter或setter方法,不允许使用writable和value这两个属性
  • setter :使用了getter或setter方法,不允许使用writable和value这两个属性。
const obj = {
    
    };
Object.defineProperty(obj, 'name', {
    
    
    enumerable: true,
    configurable: true,
    getter:function(){
    
    },
    setter: function(){
    
    }
})

由于Object.defineProperty不兼容IE8及以下浏览器,所以Vue不兼容IE8及以下浏览器。

将数据变为响应式数据

new Vue_init过程中,有个initState方法,它主要是对 propsmethodsdatacomputedwathcer 等属性做了初始化操作。每个初始化的具体代码就不贴了,主要提一下propsdata的处理方法中。

在处理props时,主要做了以下两个步骤:

  • defineReactiveprops的每个值添加gettersetter方法;
  • proxyvm._props.xxx 的访问代理到 vm.xxx 上。

在处理data时,主要是:

  • 通过 proxy 把每一个值 vm._data.xxx 都代理到 vm.xxx 上;
  • 另一个是调用 observe 方法观测整个 data 的变化。

observe方法是干什么的?

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 尝试为一个值创建一个观察者实例,
 * 如果成功观察到,则返回新的观察者,
 * 如果值已经有一个,则返回现有的观察者。
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
    
    
  if (!isObject(value) || value instanceof VNode) {
    
    
    return
  }
  let ob: Observer | void
  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
  ) {
    
    
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    
    
    ob.vmCount++
  }
  return ob
}

observe 方法的作用就是给非 VNode 的对象类型数据添加一个 Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个 Observer 对象实例。接下来我们来看一下 Observer 的作用。

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 * 附加到每个被观察对象的观察者类。
 * 一旦附加,观察者将目标对象的属性键转换为getter/setter,
 * 以收集依赖项并分派更新。
 */
export class Observer {
    
    
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    
    
    this.value = value
    // 创建一个Dep实例,Dep下面会介绍
    this.dep = new Dep()
    this.vmCount = 0
    // 把自身实例添加到数据对象 value 的 __ob__ 属性上
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
    
    
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      // 对数组会调用 observeArray 方法,即递归调用observe
      this.observeArray(value)
    } else {
    
    
      // 纯对象调用 walk 方法,为对象每个属性调用defineReactive方法
      this.walk(value)
    }
  }

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    
    
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
    
    
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    
    
    for (let i = 0, l = items.length; i < l; i++) {
    
    
      observe(items[i])
    }
  }
}

由上面的分析可知,在处理data时,我们会调用observe方法,在这个方法中会对value进行判断,如果是数组,就递归调用observe方法;如果是对象就调用defineReactive方法。

即对propsdata的处理都

  • 调用proxy实现vm._props.xxx 和vm._data.xxxx的读写变成了 vm.xxx 的读写;
  • 调用了defineReactive方法。

defineReactive

于是我们要来学习defineReactive方法了,这个方法就是为将对象转为响应式对象,给对象新增gettersetter方法,当我们读取这些对象时,就会调用getter方法,改变他们的值时就会调用setter方法。

/**
 * Define a reactive property on an Object.
 * 在对象上定义响应式属性
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
    
    
  // 创建一个Dep实例,Dep下面会介绍
  const dep = new Dep()

  // 获得每个属性的property,因为要新增getter和setter,如果configurable是false就没得玩了
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    
    
    return
  }

  // cater for pre-defined getter/setters
  // 提供预定的  getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    
    
    val = obj[key]
  }
  
  // 对子对象递归调用 observe 方法,这样就保证了无论 obj 的结构多复杂,
  // 也会调用到defineReactive方法
  // 它的所有子属性也能变成响应式的对象,添加了getter和setter方法
  // 在分析getter和setter之前,我们得先学习一下Dep
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    
    
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    
    
	 // ...
    },
    set: function reactiveSetter (newVal) {
    
    
      // ...
    }
  })
}

Dep

Vue实现响应式中有一个重要的东西:Dep,可以把它理解成vuex中的store。它存储管理着多个watcher,这些watcher是对响应式数据的监听。每次数据变为响应式数据(watcher)时,就会调用getter告诉Dep我被监听啦,然后它就被`Dep`管理了。当响应式数据发生改变时,会调用`setter`告诉`Dep`我改变啦,然后`Dep`就会告诉`render`:有些数据改变啦,麻烦改变一下视图render就会更新视图了~~
在这里插入图片描述

我们先来看源码中Dep的实现, Dep主要有targetsubs存放多个watcher的数组,还有一些添加、删除、依赖和订阅的方法。

// src/core/observer/dep.js
import type Watcher from './watcher'
import {
    
     remove } from '../util/index'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 * dep是一个可观察的,可以有多个
 */
export default class Dep {
    
    
  // target这是一个全局唯一 Watcher, 在同一时间只能有一个全局的 Watcher 被计算
  static target: ?Watcher;
  id: number;
  // subs 也是 Watcher 的数组
  subs: Array<Watcher>;

  constructor () {
    
    
    this.id = uid++
    this.subs = []
  }

  // 新增
  addSub (sub: Watcher) {
    
    
    this.subs.push(sub)
  }
  
  // 移除
  removeSub (sub: Watcher) {
    
    
    remove(this.subs, sub)
  }
  
  // 依赖
  depend () {
    
    
    if (Dep.target) {
    
    
      Dep.target.addDep(this)
    }
  }
  
  // 通知/订阅
  notify () {
    
    
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
    
    
      subs[i].update()
    }
  }
}

Dep.target = null
const targetStack = []

export function pushTarget (_target: ?Watcher) {
    
    
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
    
    
  Dep.target = targetStack.pop()
}

Dep离不开Watcher

Watcher

_initState时,我们的响应式数据已经有了gettersetter方法,什么时候会调用getter方法呢?

_render的过程中我们会去实例化一个Watcher对象。

// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
    
    
before () {
    
    
  if (vm._isMounted && !vm._isDestroyed) {
    
    
    callHook(vm, 'beforeUpdate')
  }
}
}, true /* isRenderWatcher */)

进入到Watcher中,赋值各种值,然后调用了get方法

let uid = 0

/**
 * A watcher parses an expression,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 * watcher 监视程序解析一个表达式,收集依赖项
 * 并在表达式值改变时触发回调
 * 这在$watch() api和指令中都使用
 */
export default class Watcher {
    
    
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  // watcher分多种
  // 还记得vue中watch的deep吗,就是这个
  // [1] deep watcher:深度观测
  // 当deep为true时,会调用traverse函数,对一个对象做深层递归遍历,历过程中就是对一个子对象的访问,会触发它们的 getter 过程,这样就可以收集到依赖
  deep: boolean;  
  // [2] user watcher
  // 在对 watcher 求值以及在执行回调函数的时候,会处理一下错误
  user: boolean;
  // [3] computed watcher
  // 为计算属性量身定制的
  lazy: boolean;
  // [4] sync watcher
  // 由于数据更新是异步的,但是比如:value.sync=""时,value更新,会同步更新
  sync: boolean;
  dirty: boolean; 
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    
    
    this.vm = vm
    if (isRenderWatcher) {
    
    
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
    
    
      this.deep = !!options.deep  // 是否是 deep Watcher
      this.user = !!options.user  // 是否是 user Watcher
      this.lazy = !!options.lazy  // 是否是 computed watcher
      this.sync = !!options.sync  // 是否是同步更新
      this.before = options.before
    } else {
    
    
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []   // deps 表示上一次添加的 Dep 实例数组
    this.newDeps = [] // newDeps 表示新添加的 Dep 实例数组
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
    
    
      this.getter = expOrFn
    } else {
    
    
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
    
    
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${
      
      expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    
    
    // pushTarget 的定义在 src/core/observer/dep.js 中
    // export function pushTarget (_target: Watcher) {
    
    
    //  if (Dep.target) targetStack.push(Dep.target)
    // 	Dep.target = _target
    // }
    // 实际上就是把 Dep.target 赋值为当前的渲染 watcher 并压栈(为了恢复用)
    pushTarget(this)
    let value
    const vm = this.vm
    try {
    
    
      value = this.getter.call(vm, vm)
      // 在上面有这么一行代码: this.getter = expOrFn
      // 即调用updateComponent
      // vm._update(vm._render(), hydrating)
      // 在渲染VNode的过程中就会对数据进行访问,所以就访问到了数据的getter方法
      // 开始来分析一下getter方法:
        
      // get: function reactiveGetter () {
    
    
      //    const value = getter ? getter.call(obj) : val
      //    【注释】Dep.target是当前的watcher
      //    if (Dep.target) {
    
    
      //    【注释】dep.depend即是调用了Dep.target.addDep(this);
      //    【注释】又由于Dep.target是当前的watcher所以就是调用了addDep,所以要去分析addDep了
      //    【注释】当前的 watcher 订阅到这个数据持有的 dep 的 subs 中
      //    dep.depend()
      //    if (childOb) {
    
    
      //      childOb.dep.depend()
      //      if (Array.isArray(value)) {
    
    
      //       dependArray(value)
      //     }
      //    }
      // }
      //  return value
      //  },
    } 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
      // 递归去访问 value,触发它所有子项的 getter
      if (this.deep) {
    
    
        traverse(value)
      }
      // Dep.target = targetStack.pop()
      // 将当前watcher推出
      popTarget()
      // 依赖清空
      // 在执行 cleanupDeps 函数的时候,
      // 首先遍历 deps,移除对 dep.subs 数组中 Wathcer 的订阅,
      // 然后把 newDepIds 和 depIds 交换,newDeps 和 deps 交换,
      // 并把 newDepIds 和 newDeps 清空
      // 保证在切换视图时,不会对旧视图的数据还进行监控
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  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)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    
    
    let i = this.deps.length
    while (i--) {
    
    
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
    
    
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

}

关于依赖收集就写到这里,画了一张图消化一下,唔知有没有错误:
在这里插入图片描述
init时,我们会调用initState方式去初始化propsdatacomputed等内容,其中以propsdata为例,发现主要是使用defineReactive方法给这些数据加上了gettersetter方法。之后我们在$mount方法中,创建了Watcher实例,这个实例调用了_render方法,会读取到数据,于是将会触发数据上的getter方法,在getter方法上,将当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,然后清空依赖完成了依赖收集

猜你喜欢

转载自blog.csdn.net/qq_34086980/article/details/105944142