Vue 2.6 源码剖析-响应式原理学习 - 3.数据响应式原理

数据响应式原理

数据响应式 和 双向绑定机制 是使用数据驱动开发的基石。

数据响应式:当数据发生变化,自动更新视图,不需要手动操作DOM。

响应式处理入口

整个响应式处理的过程是比较复杂的。

  • 在构造函数中,调用了 src/core/instance/init.js 中的 _init 方法
  • 在 _init 方法中调用了 src/core/instance/state.js中定义的 initState(vm):初始化 Vue 实例的状态
    • 初始化了 data、props、methods等
  • initState(vm) 方法中判断:
    • 如果定义了 data 就调用 initData(vm)
      • 把 data 中的成员注入到 Vue 实例,并且把它转换成响应式对象。
      • initData() 方法最后调用了 observe() 方法
    • 如果没有定义 data,就直接调用 observe()
      • 初始化一个空的响应式对象
  • observe 就是响应式处理的入口

observe()

observe() 方法在src\core\observer\index.js中定义。

observer 文件夹中的文件,都是和响应式处理相关的。

observe(value)的内容:

  1. 校验 value 不是对象,或是 VNode 实例,则不处理
  2. 判断如果 value 做过响应式的处理,是则直接返回它的 Observer 对象 value.__ob__
    1. 相当于做了一个缓存
  3. 判断它是否可以进行响应式处理
    1. 主要判断value是否是 Array 或 纯粹的 Object,以及不是 Vue 实例。
  4. 判断通过,则创建一个 Observer 对象并返回。
    1. 如果 value 是根数据,还要计数。
// src\core\observer\index.js
/**
 * 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.
 * 试图为 value 创建一个 Observer 对象,
 * 如果创建成功,会返回这个 Observer 对象,
 * 或者直接返回已经创建过的 Observer 对象
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 判断 value 不是对象 或者 是 VNode 的实例,就不需要做任何处理
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  // 创建一个 Observer 类型的变量
  let ob: Observer | void
  // 判断 value 中是否有 __ob__ 属性,并且这个属性是否是 Observer 的实例
  // 判断 value 是否做过响应式的处理,相当于缓存
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    // 满足条件,获取 __ob__ 并最终返回
    ob = value.__ob__
  } else if (
    // 否则就创建 ob
    // 创建之前进行判断,判断当前对象是否可以进行响应式的处理
    // 判断依据:
    // 1. shouldObserve 某些情况会通过设置false禁用响应,这是逻辑上的优化.例如实例上定义props,没有意义但是不会报错
    // 2. isServerRendering 判断是否是服务器渲染
    // 3. Array.isArray(value) 判断是否是数组
    // 4. isPlainObject 判断是否是纯粹的 [object object] 对象
    // 5. _isVue 是否是 Vue 实例
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 创建一个 Observer 对象
    // 在创建过程中,最终会把value对象转化成一个响应式对象
    ob = new Observer(value)
  }
  // 如果当前处理的是根数据,进行计数
  if (asRootData && ob) {
    ob.vmCount++
  }
  // 返回 Observer 对象
  return ob
}

Observer

// src\core\observer\index.js
/**
 * 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.
 * Observer 类被附加到每一个被观察的对象,
 * 一旦附加,Observer对象就会将目标对象的所有属性转换成 getter/setter,
 * 用于收集依赖 和 派发更新(发送通知)
 */
export class Observer {
  // 声明3个属性并指定属性的类型
  // 在 class 顶部声明属性是 ES6 的语法
  // 为属性指定类型是 Flow 的语法

  // 观测对象
  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
    // def:Object.defineProperty
    // 将实例 this 挂载到观察对象 value 的 __ob__ 属性
    def(value, '__ob__', this)
    // 判断 value 是否是数组
    if (Array.isArray(value)) {
      // 对数组类型做响应式处理
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 为数组中的每一个对象元素创建一个 observer 实例
      // 如果元素不是对象,observe 不会作处理
      this.observeArray(value)
    } else {
      // 对object对象做响应式处理
      // 遍历对象中的每一个属性,转换成 setter/getter
      this.walk(value)
    }
  }

  /**
   * Walk through all properties 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)
    // 遍历每一个属性,调用 defineReactive 设置为响应式数据
    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])
    }
  }
}

defineReactive

// src\core\observer\index.js
/**
 * Define a reactive property on an Object.
 * 为一个对象定义一个响应式的属性
 * @param shallow 浅层(true)/深度(false)监听
 *  true:只监听对象第一层的属性
 *  false:监听对象中的每一层每一个属性)
 * @param customSetter 用户自定义的setter函数
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 创建依赖对象实例
  // 用于为当前属性收集依赖(watcher)
  const dep = new Dep()

  // 获取属性的描述符对象
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // 判断属性是否可配置
  if (property && property.configurable === false) {
    return
  }

  // 提供预定义的存取器函数
  // 如果obj是用户定义的对象,可能自定义了get/set
  // 所以获取用户定义的get/set,在其基础上扩展 收集依赖 和 派发更新 的功能
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  // 获取初始值
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 判断是否递归观察子对象(深度监听),则将子对象属性转换成响应式,并返回子观察对象
  let childOb = !shallow && observe(val)
  // 把属性转换成 getter/setter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 首先判断是否预定义了getter
      // 如果存在则获取并返回getter调用的返回值
      // 如果不存在则直接返回之前获取的 初始值
      const value = getter ? getter.call(obj) : val

      // 收集依赖的处理:
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      // 返回属性的值
      return value
    },
    set: function reactiveSetter (newVal) {
      // 同get一样先获取旧值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      // 判断值是否发生了变化
      // newVal !== newVal && value !== value 用于判断新旧值为 NaN 的情况
      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
      // 判断是否只读属性
      if (getter && !setter) return

      if (setter) {
        // 存在预定义的setter则调用
        setter.call(obj, newVal)
      } else {
        // 没有getter和setter,直接赋值
        val = newVal
      }
      // 如果新值是对象,并且是深层监听
      // 则将这个对象转化为响应式
      // 并返回给 childOb
      childOb = !shallow && observe(newVal)
      // 派发更新(发布通知)
      dep.notify()
    }
  })
}

依赖收集

在defineReactive中定义了属性的getter/setter。

当访问(getter)这个属性的时候,进行了依赖收集。

依赖收集就是将依赖该属性的 watcher 对象添加到 Dep对象的 subs 数组中。

将来,数据发生变化的时候去通知所有的 watcher。

// src\core\observer\index.js
get: function reactiveGetter () {
  // 首先判断是否预定义了getter
  // 如果存在则获取并返回getter调用的返回值
  // 如果不存在则直接返回之前获取的 初始值
  const value = getter ? getter.call(obj) : val

  // 收集依赖的处理:
  // 如果存在当前依赖目标,即 watcher 对象,则建立依赖
  if (Dep.target) {
    // depend 进行依赖的收集,向subs添加watcher

    // 这里是 defineReactive 创建的 dep:负责收集当前 key 属性的依赖
    dep.depend()
    // 如果子观察目标存在,建立子对象的依赖关系
    if (childOb) {
      // 每个 observer 对象都有一个dep属性:new Dep()
      // 调用 depend 让子对象收集依赖

      // 这里是 observer 对象创建的 dep:负责收集 自己(当前是子对象) 的依赖
      // 用于监听子对象成员的 添加/删除 变化
      childOb.dep.depend()
      // 如果值是数组,则特殊处理收集数组对象依赖
      if (Array.isArray(value)) {
        dependArray(value)
      }
    }
  }
  // 返回属性的值
  return value
},
设置 Dep.target 的位置

设置 Dep.target 是在创建 watcher 对象的位置:mountComponent()

// src\core\instance\lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // ...
  
  // 创建 Watcher 对象,并传入 updateComponent 方法
  // updateComponent 是在 Watcher 中调用的
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  
  // ...
}

在 Watcher 的构造函数中调用了 get() 方法,get() 放开中调用了 pushTarget 方法,内部将自己记录到了 Dep.target。

// src\core\observer\watcher.js
export default class Watcher {
  // ...

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    // ...
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  get () {
    // 把当前的 Watcher 对象存入到 targetStack(target栈)中
    // pushTarget的作用:
    //  每一个组件都会对应一个 Watcher,Watcher会渲染视图
    //  如果组件有嵌套的话会先渲染内部的组件
    //  同一时间Dep.target只存储一个watcher
    //  所以它要把父组件对应的Watcher先保存起来
    // 它还负责给 Dep.target 赋值,用于为响应式数据收集依赖
    pushTarget(this)
    
    // ...
  }
}
// src\core\observer\dep.js
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
// Dep.target 用来存放目前正在使用的 watcher 目标对象
// 它是全局唯一的,同一时间只有一个 watcher 被使用
Dep.target = null
const targetStack = []

// 入栈并将当前 watcher 赋值给 Dep.target
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  // 出栈操作
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
使用栈(targetStack)的目的

Vue 2 以后每一个组件都对应一个watcher对象:

  • 每个组件都会调用mountComponent,在其中创建一个watcher对象

如果组件有嵌套的话,例如A组件嵌套B组件。

当渲染A组件的时候发现,自己还有子组件。

于是要先去渲染子组件。

此时A组件的渲染过程就被挂载起来。

所以A组件所对应的watcher对象也应该被存储起来(存储到栈中)。

当子组件渲染完成后,会把自己对应的watcher从栈中弹出(popTarget)。

继续去执行父组件的渲染。

dep.depend()

depend 用于将watcher添加到Dep.subs数组中。

它内部调用了Dep.target(也就是 watcher )的addDep方法。

// src\core\observer\dep.js
/* @flow */

import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'

let uid = 0

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 * dep 是一个可观察对象,可以有多个指令订阅它
 */
export default class Dep {
  // 静态属性:watcher 类型
  static target: ?Watcher;
  // dep 实例 id
  id: number;
  // dep 实例对应的 watcher 对象/订阅者数组
  subs: Array<Watcher>;

  constructor () {
    // 每创建一个 Dep 对象就更新id,作为唯一标识
    this.id = uid++
    this.subs = []
  }

  // 添加新的订阅者 watcher 对象
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  // 移除订阅者
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  // 将观察对象和 watcher 建立依赖
  depend () {
    if (Dep.target) {
      // 如果 target 存在,把 dep 对象添加到 watcher 的依赖中
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
// Dep.target 用来存放目前正在使用的 watcher 目标对象
// 它是全局唯一的,同一时间只有一个 watcher 被使用
Dep.target = null
const targetStack = []

// 入栈并将当前 watcher 赋值给 Dep.target
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  // 出栈操作
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
// src\core\observer\watcher.js
/**
   * Add a dependency to this directive.
   */
addDep (dep: Dep) {
  // 获取dep对象的唯一标识
  const id = dep.id
    // newDepIds newDeps 用于存储watcher对象依赖的所有dep,最后会被清空
    // depIds deps 保留最终的结果
  // 判断是否已经依赖了当前dep对象
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
      // 调用dep对象的addSub方法把当前watcher对象添加到subs数组
      dep.addSub(this)
    }
  }
}
总结
  1. 首先 mountComponent 挂载组件(根实例也算作组件)的时候会给组件创建 Watcher,并传入 updateComponent 方法作为组件的 getter
  2. Watcher 构造函数中:
    1. 将传入的 updateComponent 设置为组件的 getter
    2. 调用自身的 get 方法
  3. Watcher 对象自身的 get方法:
    1. 把自己赋值给 Dep.target
    2. 调用了组件的 getter方法,也就是 updateComponent
    3. updateComponent 中调用了 vm._render 函数
      1. renderMixin中初始化了 Vue 原型上的 _render函数
      2. _render函数:调用用户传入的render或编译器生成的render
      3. render函数内部调用 h 函数(_c:createElement)创建虚拟DOM
        1. 过程中会访问 data 中的属性
  4. data 的属性转化的 get 方法中进行了依赖收集
    1. 每次访问 data 的属性的时候都会执行属性的 get 方法,数据变化后也会执行get方法(set中访问了属性)
    2. 也就是当访问这个属性的时候,会进行收集依赖
  5. 所以首次渲染 和 创建 Watcher 的时候就会进行依赖收集
    1. 如果 Dep.target 存在,调用 dep.depend 方法将 Dep.target 中存储的 watcher 对象添加到 Dep.subs 数组中

依赖收集调试

调试代码
<div id="app">
  <h1>{{ msg }}</h1>

  {{ msg }}

  <hr />

  {{ count }}
</div>
<script src="../../dist/vue.js"></script>
<script>
  const vm = new Vue({
    el: "#app",
    data: {
      msg: "Hello Vue",
      count: 100,
    },
  });
</script>
断点位置

断点设置在创建 Watcher 对象的位置:src\core\instance\lifecycle.js 文件的 mountComponent 方法中。

调试过程

调试过程中会在VM开头的文件中查看 render 函数运行的内容,里面使用了初始化的以下划线_开头的方法:

  • _c:h函数 (createElement)
  • _s:toString,用于把属性获取到的值转化成字符串
  • _v:创建文本的虚拟节点(textVNode)

数组

在创建 Observer 对象中,判断了如果观测对象是数组类型,则执行数组的响应式处理。

Vue 会先判断当前浏览器是否支持 __proto__,然后执行对应的处理。

// src\core\observer\index.js
export class Observer {
  // 观测对象
  value: any;
  // 依赖对象
  dep: Dep;
  // 实例计数器
  vmCount: number;

  constructor (value: any) {
    
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    
    def(value, '__ob__', this)
    
    // 判断 value 是否是数组
    if (Array.isArray(value)) {
      // 对数组类型做响应式处理
      // hasProto 判断当前浏览器是否支持 __proto__
      // arrayMethods 是包含扩展后的数组原生方法的对象
      if (hasProto) {
        // 通过__proto__直接在原型上挂载修改后的方法
        protoAugment(value, arrayMethods)
      } else {
        // arrayKeys 是 arrayMethods 的 keys
        // 遍历 arrayMethods 通过 defineProperty 挂载方法
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 为数组中的每一个对象元素创建一个 observer 实例
      // 如果元素不是对象,observe 不会作处理
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  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])
    }
  }
}
arrayMethods 数组方法扩展

Vue 通过使用数组的原型创建了一个包含数组相关方法的对象:arrayMethods。

Vue 对数组中会修改到自身的原生方法进行了扩展:

  1. 首先调用数组的原始方法
  2. 如果数组变化是新增,获取新增的元素,将新增的元素转化为响应式数据
  3. observer对象发送通知
// src\core\observer\array.js
/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

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]
  // 向 arrayMethods 重新注册方法
  def(arrayMethods, method, function mutator (...args) {
    // 首先执行数组的原始方法
    const result = original.apply(this, args)
    // 获取数组对象的 ob 对象
    const ob = this.__ob__
    // inserted 用于存储数组中新增的元素
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 判断是否有新增的元素,排除splice用于移除元素的情况
    // observeArray:遍历数组并且把数组的每一个对象元素转化成响应式对象
    // 如果元素不是对象,observe 不会作处理
    if (inserted) ob.observeArray(inserted)
    // notify change
    // 调用 observer 对象中 dep 的 notify 方法发送通知
    ob.dep.notify()
    return result
  })
})

protoAugment 直接修改数组的原型方法

直接修改数组的原型属性 __proto__,将修改后的方法挂载到数组上。

// src\core\observer\index.js
/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 * 通过截取原型链来扩充目标对象或数组
 */
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
copyAugment Object.defineProperty 挂载方法

遍历 arrayMethods 通过 Object.defineProperty 挂载方法

// src\core\observer\index.js
/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
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])
  }
}

数组练习

下面来看一段和数组响应式相关的代码,通过源码的角度来解释它的执行结果。

<div id="app">
  {{ arr }}
</div>
<script src="../../dist/vue.js"></script>
<script>
  const vm = new Vue({
    el: "#app",
    data: {
      arr: [2, 3, 5],
    },
  });

// 在控制台执行下面的代码
// vm.arr.push(8)
// vm.arr[0] = 100
// vm.arr.length = 0
  
// vm.arr.unshift({num: 0})
// vm.arr[0].num = 100
</script>

在控制台打印 vm.arr,查看它的原型__propto__,包含扩展后的方法:mutator

vm.arr.__proto__.__proto__ 中的方法才是数组中原生的方法。

在这里插入图片描述

测试代码结果:

  1. vm.arr.push(8) 更新了试图
    1. 因为调用了扩展后的push方法,在修改数组后,发送通知更新了试图
    2. 同理,vm.arr.unshift({num:0}) 也会更新视图。
  2. vm.arr[0]=100 数组发生了变化,但没有更新试图
    1. 通过源码发现,没有监听数组中属性或元素的值的变化
      1. 所以 vm.arr.length=0 也不会触发试图更新
    2. 而是将对象类型的数组元素转换成了响应式对象
      1. 所以如果元素是对象,修改对象的属性的值,会触发试图更新
      2. 所以 vm.arr[0].num 的变更会触发更新

Vue 没有监听数组属性或元素的原因:数组的元素可能非常的多,如果处理的话可能会影响性能。

Vue 提供 splice 实现对数组元素的修改。

Watcher

Watcher 分为三种:

  • Computed Watcher
    • initState 中调用 initComputed 初始化
  • 用户 Watcher(侦听器)
    • initState 中调用 initWatch 初始化
    • vm.$watch添加的侦听器
  • 渲染 Watcher
    • 首次渲染的时候初始化:mountComponent
渲染 Watcher

首次渲染时在 mountComponent 中创建了渲染 Watcher。

  • 创建之前首先创建了 updateComponent 方法。
    • 内部调用了 _render 和 _update 方法:
      • _render:调用用户传入的 render 或 编译器生成的 render,创建 VNode
      • _update:调用 patch 函数生成真实DOM,渲染到视图
  • 然后 new 创建 Watcher 对象
    • 传入了5个参数:
      • vm:Vue 实例
      • updateComponent:接收通知时触发的方法,用于更新真实DOM
      • noop:一个空函数,主要用于计算属性和侦听器,渲染Watcher用不到
      • before方法:用于触发 beforeUpdate 钩子函数
      • true:isRenderWatcher,用于标识当前是渲染 Watcher
    • 创建的时候传入了 updateComponent
// src\core\observer\watcher.js
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  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
    // 是否是 渲染 Watcher
    if (isRenderWatcher) {
      // 把渲染 Watcher 记录到 _watcher
      vm._watcher = this
    }
    // 把渲染 watcher、计算属性、侦听器,记录到 _watchers
    // 这里存储的是所有的 watcher
    vm._watchers.push(this)

    // 下面定义了与渲染watcher无关的属性
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      // Watcehr中最终要更新视图
      // lazy 表示是否延迟更新视图
      // 计算属性的Watcher:lazy为true
      // 因为计算属性要在数据变化后才会更新视图
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    // watcher 的唯一标识
    this.id = ++uid // uid for batching
    // 标识当前 watcher 是否是活动的
    this.active = true

    // dirty 用于缓存 计算属性watcher 的值
    // 计算属性watcher的lazy为true,这里初始化dirty为true
    // 当计算属性watcher的getter触发时,会判断dirty
    //   如果dirty为false,直接获取它的缓存值:watcher.value
    //   如果dirty为true,则调用 watcher.evaluate -> watcher.get 获取,并把dirty设置false
    // 当计算属性watcher的update触发时,会将dirty设置为true
    this.dirty = this.lazy // for lazy watchers
    // 下面4个用于记录和 watcher 相关的 dep 对象
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    // 设置 watcher 的 getter,稍后会在 get 方法中调用
    if (typeof expOrFn === 'function') {
      // 如果 expOrFn 是方法,直接设置为getter
      this.getter = expOrFn
    } else {
      // 侦听器的 Watcher 会传入字符串(侦听的属性),例如'person.name'
      // parsePath('person.name') 返回一个函数,用于获取 person.name 的值
      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
        )
      }
    }
    // 判断是否延迟执行
    // get中调用了当前组件的getter,并将this指向Vue实例
    // 将get的返回值记录到 this.value中
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    // 把当前的 Watcher 对象存入到 targetStack(target栈)中
    // pushTarget的作用:
    //  每一个组件都会对应一个 Watcher,Watcher会渲染视图
    //  如果组件有嵌套的话会先渲染内部的组件
    //  所以它要把父组件对应的Watcher先保存起来
    // 它还负责给 Dep.target 赋值,用于为响应式数据收集依赖
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 这里是 get 方法的核心:
      // 调用 getter
      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)
      }
      // 将当前 watcher 从栈里弹出
      popTarget()
      // 清理依赖项集合
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    // 获取dep对象的唯一标识
    const id = dep.id
    // newDepIds newDeps 用于存储watcher对象依赖的所有dep,最后会被清空
    // depIds deps 保留最终的结果
    // 判断是否已经依赖了当前dep对象
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // 调用dep对象的addSub方法把当前watcher对象添加到subs数组
        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)
      }
    }
    // 清空 newDepIds 和 newDeps
    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
  }

	// ...
}
当数据更新的时候,watcher如何工作
  • 数据更新后会调用 dep 对象的 notify 方法。
  • 在notify方法中调用了 watcher 的 update。
  • update调用queueWatcher
    • 先把watcher放到队列中
    • 然后遍历这个队列,去调用每个watcher的run方法
  • run 方法中最终
    • 渲染 watcher:调用了 watcher 的updateComponent 函数
    • 其他 watcher:调用了用户定义的回调函数
// src\core\observer\dep.js
notify () {
  // stabilize the subscriber list first
  // 克隆 subs 的副本
  // 目的:
  // 1. 稍后给subs排序
  // 2. 在处理所有的watcher对象的过程中,有可能会给 this.subs 数组中增加新的 watcher 对象
  //    而新增加的 watcher 对象目前是不作处理的
  const subs = this.subs.slice()
  if (process.env.NODE_ENV !== 'production' && !config.async) {
    // subs aren't sorted in scheduler if not running async
    // we need to sort them now to make sure they fire in correct
    // order
    // 给 watcher 列表排序
    // 按照watcher创建的顺序,保证watcher的执行顺序是正确的
    subs.sort((a, b) => a.id - b.id)
  }
  // 循环调用 watcher 的update 实现更新
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}
// src\core\observer\watcher.js
/**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    // 渲染 watcher 的 lazy 和 sync 默认为false
    // queueWatcher的核心作用是把当前watcher放到一个队列中
    queueWatcher(this)
  }
}
// src\core\observer\scheduler.js
/**
 * Push a watcher into the watcher queue.
 * Jobs with duplicate IDs will be skipped unless it's
 * pushed when the queue is being flushed.
 */
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  // 判断 watcher 是否正在被处理,防止重复处理
  if (has[id] == null) {
    // 标记当前 watcher 正在处理
    has[id] = true

    // 把当前要处理的 watcher 放到队列中
    // flushing 表示正在刷新的意思
    // flushing 为 true 表示queue队列正在被处理
    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.
      // 如果当前队列正在被处理,则找一个合适的位置插入 watcher
      let i = queue.length - 1
      // index 是当前队列处理的元素位置
      // i>index 表示当前队列还没有被处理完
      // 要将id较小的watcher插入到前面
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    // 执行队列中的 watcher
    // waiting为true表示当前队列正在被执行
    if (!waiting) {
      waiting = true

      // 生产环境直接调用 flushSchedulerQueue
      // 开发环境将 flushSchedulerQueue 传给 nextTick中调用
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

/**
 * Flush both queues and run the watchers.
 */
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 排序
  // 排序的目的:
  // 1. 组件更新的顺序是从父组件到子组件
  //   (因为父组件永远在子组件之前创建)
  // 2. 组件的用户 watchers 要在它对应的渲染 watcher之前执行
  //   (因为用户watchers是在渲染watcher之前创建的)
  //   (initState[创建用户watchers]是在mountComponent[创建渲染watcher]之前执行的)
  // 3. 如果一个组件在父组件执行之前被销毁了,则可以跳过这个watcher
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  // 不要去缓存length
  // 因为当watchers执行的过程中,还有可能向队列中放入新的watcher
  // 这是 index=0 而不是 index=query.length 的原因
  for (index = 0; index < queue.length; index++) {
    // 获取watcher
    watcher = queue[index]
    // 如果有before函数则执行
    // 只有渲染watcher才有before,它的作用是用来触发beforeUpdate钩子函数
    if (watcher.before) {
      watcher.before()
    }
    // 获取 id
    id = watcher.id
    // 重置 watcher 的处理状态
    // 当数据变化后,下一次的时候watcher还能正常被运行
    has[id] = null
    // 调用 run
    watcher.run()

    // 后面是一些辅助性的工作
    // ...
  }
  
  // keep copies of post queues before resetting state
  // 在清理前,先备份两个队列
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  // 清空 索引index、activatedQueue、updatedQueue、has
  // 并重置 waiting 和 flushing
  resetSchedulerState()

  // call component updated and activated hooks
  // 触发生命周期钩子函数:activated 和 updated
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}
// src\core\observer\watcher.js
/**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
run () {
  // 判断当前 watcher 对象是否是存活的状态
  if (this.active) {
    // 调用 watcher 的 get 方法,并记录返回结果
    // 对渲染 watcher 来说 updateComponent 是没有返回值的
    // 所以渲染 watcher 在这里 value = undefined
    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
      // 判断如果当前是用户watcher
      if (this.user) {
        // 就调用它的 cb 回调函数(例如侦听器对应的function)
        // 使用try catch 是避免用户定义的回调发生异常
        try {
          this.cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "${this.expression}"`)
        }
      } else {
        // 渲染 watcher 的cb是noop(空函数)
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}

调试数组的响应式数据执行过程

调制重点:

  1. 数组响应式处理的核心过程 和 数组收集依赖的过程
    1. 首先会给属性创建一个Observer,为属性收集依赖
    2. 也就是当属性的值发生变化,会通知watcher
    3. 其次判断这个属性的值如果是对象,就创建一个Observer,为这个对象收集依赖
    4. 如果这个属性的值是数组,还要递归(dependArray)为这个数组中的每一个对象或数组类型的元素收集依赖
    5. 注意:整个过程并没有为数组的属性(通过索引访问的元素 或 length)收集依赖。
  2. 当数组的数据改变的时候 watcher 的执行过程
<div id="app">
  {{ arr }}
</div>
<script src="../../dist/vue.js"></script>
<script>
  const vm = new Vue({
    el: "#app",
    data: {
      arr: [2, 3, 5],
    },
  });

  // 在控制台执行下面的代码
  // vm.arr.push(8)
</script>

断点位置:

  • 调试重点1 src/core/observer/index.js
    • observe:响应式入口函数中创建 Observer 对象的位置
    • defineReactive:收集依赖的位置,定义get方法的位置
  • 调试重点2 /src/core/observer/dep.js
    • dep对象的notify方法:数据发生变化会发送的通知

响应式处理过程 - 总结

  1. 整个响应式是从 Vue 的_init开始的
    1. 在 _init 方法中 调用了 initState 初始化 Vue 的状态
    2. initState 方法中调用了 initData
      1. 把 data 属性注入到 Vue 实例上
      2. 并且调用 observe 把 data 对象转化成响应式对象
    3. observe 就是响应式的入口
  2. observe(value) 接收一个参数:响应式要处理的对象
    1. 首先判断 value 是否是对象,如果不是对象,直接返回
    2. 判断value对象是否有 __ob__(表示之前已经做过响应式的处理),如果有直接返回
    3. 如果没有,创建 Observer 对象
    4. 最终返回 Observer对象
  3. Observer 类的构造函数中
    1. 首先给 value 对象定义不可枚举的 __ob__ 属性,并把当前的 Observer 对象记录到这个属性中
    2. 然后进行
      1. 数组的响应式处理
        1. 设置并重定义数组的可以改变原数组的方法(arrayMethods)
          1. 当数组发生变化发送通知(找到这个数组的__ob__ 下的 dep,调用dep.notify)
        2. 然后会遍历数组中的每一个成员,对每一个成员调用 observer
          1. 如果数组的元素是对象或数组,对它们进行响应式处理
      2. 对象的响应式处理
        1. 调用walk:遍历对象的所有属性,对每一个属性调用 defineReactive
  4. defineReactive
    1. 为每一个属性创建 dep 对象
    2. 如果当前属性的值是对象,调用 observe 把它转化成响应式对象
    3. 定义 getter
      1. 收集依赖:为每一个属性收集依赖
        1. 如果属性的值是对象,也要为子对象收集依赖
      2. 返回属性的值
    4. 定义 setter
      1. 保存新值
      2. 如果新值是对象,调用 observe 把它转化成响应式对象
      3. 派发更新(发送通知),调用 dep.notify
  5. 收集依赖的过程
    1. 首先会执行 watcher 对象的get方法
    2. get 方法中调用 pushTarget 把当前 watcher 对象记录到Dep.target 属性
    3. 然后调用 watcher 的 getter 方法
      1. 渲染 watcher 就是调用 updateCpmonent 创建VNode并更新视图,过程中会访问 data 中的成员
    4. 访问 data 中的成员的时候会触发 defineReactive 中的 getter 方法,在getter中会去收集依赖(调用dep.depend())
    5. 把属性对应的 watcher 对象 添加到 dep的 subs 数组中
      1. 这是为属性收集依赖
    6. 如果属性的值是对象或数组,就会给子对象创建一个Observer对象(childObj),然后给 childObj 收集依赖
      1. 目的是将来子对象发生变化的时候,可以发送通知
      2. 这是给子对象(对象类型的值)收集依赖
  6. Watcher 的执行过程
    1. 当数据发生变化的时候,会调用 dep.notify() 发送通知
    2. 它会调用 watcher 对象的 update() 方法
    3. update()方法中会调用 queueWatcher()
      1. 判断 watcher 是否正在被处理
        1. 如果没有,则添加到 queue 队列中,并调用 flushSchedulerQueue() 刷新(运行)任务队列
    4. flushSchedulerQueue() 中
      1. 首先触发 beforeUpdate 钩子函数
      2. 然后调用 watcher.run() 方法,内部调用的方法:
        1. get() --> getter() --> updateComponent()(渲染watcher)
        2. run方法执行完数据就更新到了视图上
      3. 接下来会执行一些清理工作
        1. 清空上一次的依赖
        2. 重置 watcher 中的一些状态
      4. 触发 activated 钩子函数
      5. updated 钩子函数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值