Vue2源码学习-响应式原理

Vue的响应式是基于数据拦截+发布订阅模式,包含了四个模块:

  1. Observer:通过Object.defineProperty或者Proxy拦截对象的getter和setter方法,从而令每个响应式数据都拥有一个Dep,当触发getter时收集依赖(使用该数据的Watcher),当触发setter时派发更新。
  2. Dep:依赖收集器,维护使用数据的各个依赖Watcher。
  3. Watcher:将视图所依赖的数据绑定到Observer的依赖收集器Dep中,当数据出现变化时触发setter,就通知调用Dep的notify方法,通知依赖该数据的各个Watcher进行update,进而触发指定对应的更新方法更新视图,最终把模型和视图关联起来。
  4. Complie:就是template模板指令解析器,对每个元素节点的指令进行扫描解析,根据指令模板替换数据,同时根据指令模板绑定Watcher的更新函数。

以微博举个可能不太恰当的例子:微博上某大佬A技术非常牛,我想想随时知道大佬A的最新动态,于是我在微博关注了大佬A。某一天大佬A发了一条动态,微博立马就会通知我。

在这个例子中,我就是一个Watcher,微博就是一个Dep,而大佬A就是一个Observer。在Watcher关注Observer的时候,Dep收集了Watcher对Observer的依赖,于是Observer更新时,就直接通过Dep通知所有的Watcher。

要理解Vue的响应式原理,我们就只需要知道Watcher怎么关注Observer,Observer如何收集Watcher的依赖,Observer如何通知Watcher。so,往下看吧。

Observer

首先我们看Observer的实现:

// 源码目录 src/core/observer/index.js

import Dep from './dep'
import VNode from '../vdom/vnode'
import {
    
     arrayMethods } from './array'
import {
    
    
  def,
  warn,
  hasOwn,
  hasProto,
  isObject,
  isPlainObject,
  isPrimitive,
  isUndef,
  isValidArrayIndex,
  isServerRendering
} from '../util/index'

// 获取Array原型对象上的所有属性名,包括不可枚举的,但不包括Symbol,其中有7种方法已被拦截
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

// 是否应该变成响应式数据,在组件内部更新的时候,我们可能不希望变成数据变成响应式
export let shouldObserve: boolean = true

// 改变shouldObserve的状态
export function toggleObserving (value: boolean) {
    
    
  shouldObserve = value
}

// Observer实现核心,修改每个属性的getter和setter,实现其依赖收集和更新派发
export class Observer {
    
    
  value: any;
  dep: Dep;
  vmCount: number; // 把这个对象作为$data的实例个数

  constructor (value: any) {
    
    
    // 对于可响应式数据对象或者数组本身
    this.value = value
    // 每个Observer自己也维护一个Dep
    this.dep = new Dep()
    // 实例数量
    this.vmCount = 0
    // 修改属性描述符,增加__ob__属性,其值为当前实例
    def(value, '__ob__', this)
    // 如果Observe的对象是数组
    if (Array.isArray(value)) {
    
    
      // 判断当前宿主环境是否实现__proto__属性
      if (hasProto) {
    
    
        // 使用__proto__替换原型对象,拦截数组原生方法。后面会讲到
        protoAugment(value, arrayMethods)
      } else {
    
    
        // 直接扩充实例的属性和方法
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 将数组变为响应式
      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])
    }
  }

  // 将数组元素变为响应式数据
  observeArray (items: Array<any>) {
    
    
    for (let i = 0, l = items.length; i < l; i++) {
    
    
      observe(items[i])
    }
  }
}

// 使用__proto__替换实例的原型对象
function protoAugment (target, src: Object) {
    
    
  target.__proto__ = src
}

// 扩充实例对象的属性
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])
  }
}

// 创建Observer实例,如果存在就直接返回,如果不存在则新建一个返回。单例模式
export function observe (value: any, asRootData: ?boolean): Observer | void {
    
    
  // 非对象类型和虚拟节点对象直接退出
  if (!isObject(value) || value instanceof VNode) {
    
    
    return
  }

  let ob: Observer | void
  // 如果有__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
  ) {
    
    
    // 生成新的Observe实例
    ob = new Observer(value)
  }
  // 这个对象是Vue实例的根$data
  if (asRootData && ob) {
    
    
    ob.vmCount++
  }
  return ob
}

// 将对象的属性变成响应式
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
    
    
  // 每个属性维护一个依赖收集器
  const dep = new Dep()

  // 获取属性描述符
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // 如果属性不可配置,即不能设置getter和setter,则跳过
  if (property && property.configurable === false) {
    
    
    return
  }

  // 有可能开发者自己重新定义了getter和setter,需要缓存一份
  const getter = property && property.get
  const setter = property && property.set
  // 没有getter或者有setter,且入参只有2个
  if ((!getter || setter) && arguments.length === 2) {
    
    
    // 当前值就是obj的key属性对应的值
    val = obj[key]
  }

  // 若属性值也是对象,深度遍历递归执行observe实例化
  let childOb = !shallow && observe(val)
  // 核心!!!设置getter和setter
  Object.defineProperty(obj, key, {
    
    
    // 可枚举
    enumerable: true,
    // 可配置
    configurable: true,
    // getter,触发getter时,收集依赖。
    get: function reactiveGetter () {
    
    
      // 获得当前值
      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
    },
    // setter,触发setter时,派发更新。newVal待设置的值
    set: function reactiveSetter (newVal) {
    
    
      // 获得当前值
      const value = getter ? getter.call(obj) : val
      // 如果值没有变化,则不触发更新通知
      if (newVal === value || (newVal !== newVal && value !== value)) {
    
    
        return
      }
      // 自定义setter方法
      if (process.env.NODE_ENV !== 'production' && customSetter) {
    
    
        customSetter()
      }
      // 如果属性不支持setter,则直接跳过
      if (getter && !setter) return
      if (setter) {
    
    
        // 有自己的setter就调用自身setter
        setter.call(obj, newVal)
      } else {
    
    
        // 更新赋值
        val = newVal
      }
      // 子对象也要重新observe实例化
      childOb = !shallow && observe(newVal)
      // 通知更新
      dep.notify()
    }
  })
}

// 也就是$set的实现,增加一个对象上没有的属性,同时将其转换为响应式
export function set (target: Array<any> | Object, key: any, val: any): any {
    
    
  // 异常检测,基础类型的值不能
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    
    
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${
      
      (target: any)}`)
  }
  // 处理数组的set
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    
    
    // 扩容
    target.length = Math.max(target.length, key)
    // 调用已经hack的splice方法
    target.splice(key, 1, val)
    // 返回值
    return val
  }
  // 如果这个属性在对象中,而且这个属性不在原型链上,证明这个是属性已经是响应式的了
  if (key in target && !(key in Object.prototype)) {
    
    
    // 直接更新属性值
    target[key] = val
    return val
  }
  // 获得对象的Observer实例
  const ob = (target: any).__ob__
  // 不允许向实例中或者$data添加响应式数据
  if (target._isVue || (ob && ob.vmCount)) {
    
    
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 该实例没有响应式数据,则不需要将其变成响应式,直接赋值返回
  if (!ob) {
    
    
    target[key] = val
    return val
  }
  // 将该属性变为响应式
  defineReactive(ob.value, key, val)
  // 通知依赖这个对象的Watcher更新
  ob.dep.notify()
  return val
}

/**
 * Delete a property and trigger change if necessary.
 */
// 删除对象上的属性
export function del (target: Array<any> | Object, key: any) {
    
    
  // 基础类型的值无法执行属性删除操作
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    
    
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${
      
      (target: any)}`)
  }
  // 数组直接调用hack后的splice方法
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    
    
    target.splice(key, 1)
    return
  }
  // 获取对象的Observer实例
  const ob = (target: any).__ob__
  // 对象可能不支持删除操作
  if (target._isVue || (ob && ob.vmCount)) {
    
    
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  // 没有该属性值,直接返回
  if (!hasOwn(target, key)) {
    
    
    return
  }
  // 删除属性
  delete target[key]
  // 该对象不是响应式的,直接返回
  if (!ob) {
    
    
    return
  }
  // 响应式的,通知更新
  ob.dep.notify()
}

// 如果触发getter,则数组的所有元素都需要触发依赖收集。因为Vue针对数组做了额外处理,不像对象一样设置getter方法
function dependArray (value: Array<any>) {
    
    
  for (let e, i = 0, l = value.length; i < l; i++) {
    
    
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
    
    
      dependArray(e)
    }
  }
}

总结:Observer的核心是observe方法,其内部维护也维护一个Dep用于收集对该对象的依赖。而observe方法的核心是通过Object.defineProperty覆写Object上各个属性的getter和setter方法,对于Object每个属性都维护一个Dep,收依赖该属性的所有目标(即Watcher),当该属性触发setter且新值与旧值不相同时,派发更新的通知。

比较特别的是数组,虽然可以通过设置下角标的getter和setter方法实现响应式数据,但是尤大出于性能考虑,而是直接hack了数组的7种方法,在调用这7种方法时触发更新,同时将数组中的元素转换为响应式数据。

此外还实现了set和方法,用于解决直接增加对象属性无法触发响应式更新的问题,对象直接使用defineReactive方法,而数组使用hack后的splice方法。del同理,删除属性也会派发更新通知。

Dep

在observer的源码中,我们可以发现一个核心就是依赖收集器,也就是Dep对象,它具体是个啥呢?我们接着看其对应的源码。

// 源码目录:src/core/observer/dep.js

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

// 每个Dep都有唯一的编号
let uid = 0

// Dep就是一个依赖收集器,一个响应式数据的依赖收集器可以被许多指令订阅
export default class Dep {
    
    
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    
    
    // Dep的唯一编号
    this.id = uid++
    // 所有的订阅者,也就是Watcher收集器
    this.subs = []
  }

  // 增加订阅者,即添加Watcher
  addSub (sub: Watcher) {
    
    
    this.subs.push(sub)
  }

  // 取消订阅
  removeSub (sub: Watcher) {
    
    
    remove(this.subs, sub)
  }

  // 添加依赖
  depend () {
    
    
    // Dep.target指向一个具体的Watcher,每个Watcher自己也维护了一个依赖收集器,表示依赖的多个数据
    if (Dep.target) {
    
    
      Dep.target.addDep(this)
    }
  }

  // 派发更新
  notify () {
    
    
    // 拷贝一份数组
    const subs = this.subs.slice()
    // 非异步情况下,保证Dep中Watcher的有序执行
    if (process.env.NODE_ENV !== 'production' && !config.async) {
    
    
      subs.sort((a, b) => a.id - b.id)
    }
    // 依次触发更新
    for (let i = 0, l = subs.length; i < l; i++) {
    
    
      subs[i].update()
    }
  }
}

// target指向依赖当前数据的Watcher。全局唯一,每一次只能有一个Wacther被添加
Dep.target = null
// Watcher栈
const targetStack = []

export function pushTarget (target: ?Watcher) {
    
    
  // 推入Watcher
  targetStack.push(target)
  // target指向推入的Watcher
  Dep.target = target
}

export function popTarget () {
    
    
  // 弹出Watcher
  targetStack.pop()
  // 指向Watcher栈栈顶
  Dep.target = targetStack[targetStack.length - 1]
}

总结:Dep的核心不复杂,说白就是两样,一个是用数组收集Watcher,一个是遍历数组通知每个Watcher进行update,而他的静态属性target是连接Wacther和Observer的桥梁。

Watcher

那么Dep收集的Wacther到底是个什么东西呢?其实就是一个依赖于该数据的订阅者,当数据发生变化时,依赖收集器Dep就是调用notify方法,触发这些watcher的update方法。这个更新方法可以是用户自定的函数,也可以是Vue模板编译后的render函数。

// 源码目录:src/core/observer/watcher.js

import {
    
    
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError,
  noop
} from '../util/index'

import {
    
     traverse } from './traverse'
import {
    
     queueWatcher } from './scheduler'
import Dep, {
    
     pushTarget, popTarget } from './dep'

import type {
    
     SimpleSet } from '../util/index'

// 全局编号,唯一性
let uid = 0

// 每个Watcher会解析一个表达式,收集其对应的数据依赖,当表达式所对应的值发生变化时就执行回调函数。
// 它也被用于$watch()和指令当中
export default class Watcher {
    
    
  vm: Component; // 组件实例
  expression: string; // 表达式
  cb: Function; // 回调函数(一般是更新函数)
  id: number; // watcher实例的唯一值
  deep: boolean; // 是否深层次数据观测,也就是watch的deep属性
  user: boolean; // user watch,$watcher就是一个user watcher,它监测到变化就执行回调函数并返回值
  lazy: boolean; // 惰性监测,computed watcher,也就是计算属性对应的watcher,它具有懒加载、缓存计算值的特性
  sync: boolean; // 是否同步更新,一旦我们设置了 sync,就可以在当前 Tick 中同步执行 watcher 的回调函数,默认是在nextTick中执行,用于优化
  dirty: boolean; // 标记watcher是否需要重新求值,用于惰性求值
  active: boolean;
  deps: Array<Dep>; // 用于收集该watcher的依赖的dep,方便一次性移除watcher。还可与lazy配合使用,惰性求值
  newDeps: Array<Dep>; // 新的依赖收集器
  depIds: SimpleSet; // 与deps配合使用,存储dep的id
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component, // 组件实例
    expOrFn: string | Function, // 表达式,取数逻辑
    cb: Function, // 回调函数(更新函数)
    options?: ?Object, // 配置选项
    isRenderWatcher?: boolean // 是否为渲染watcher。还有两类是Computed Watcher和User Watcher(watch:{}定义的),执行顺序computed->user->render,这样保证更新时数据最新。
  ) {
    
    
    this.vm = vm // 保存组件实例
    if (isRenderWatcher) {
    
    
      // 绑定实例的依赖renderWatcher
      vm._watcher = this
    }
    // 实例的维护的watcher队列
    vm._watchers.push(this)
    // watcher配置项
    if (options) {
    
    
      this.deep = !!options.deep
      this.user = !!options.user
      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
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // 将表达式解析为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
        )
      }
    }
    // 非惰性监测的情况下,触发对象属性的getter方法,收集依赖
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  // 触发getter,重新收集依赖
  get () {
    
    
    // Dep的target执行当前watcher,用于收集依赖
    pushTarget(this)
    let value
    const vm = this.vm
    try {
    
    
      value = this.getter.call(vm, vm)
    } catch (e) {
    
    
      if (this.user) {
    
    
        handleError(e, vm, `getter for watcher "${
      
      this.expression}"`)
      } else {
    
    
        throw e
      }
    } finally {
    
    
      // 深度遍历对象的每个属性,触发每个属性的getter方法,从而收集所有依赖
      if (this.deep) {
    
    
        traverse(value)
      }
      // 删除Dep上的target
      popTarget()
      // 移除当前watcher已经不再依赖的dep
      this.cleanupDeps()
    }
    return value
  }

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

  // 移除当前watcher已经不再依赖的dep
  cleanupDeps () {
    
    
    let i = this.deps.length
    while (i--) {
    
    
      const dep = this.deps[i]
      // watcher已经不再依赖的dep,直接移除
      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
  }

  /**
   * 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 {
    
    
      // 异步更新,本质是调用nextTick方法
      queueWatcher(this)
    }
  }

  // 触发回调函数
  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)
        }
      }
    }
  }

  // 针对computed watcher,当前watcher无需更新
  evaluate () {
    
    
    this.value = this.get()
    this.dirty = false
  }

  // 该watcher依赖(订阅)的所有dep
  depend () {
    
    
    let i = this.deps.length
    while (i--) {
    
    
      this.deps[i].depend()
    }
  }

  // 从所有dep中删除其依赖(订阅者)watcher
  teardown () {
    
    
    if (this.active) {
    
    
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
    
    
        // 移除实例上的watcher
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      // 从维护的deps的中移除其订阅列表中的当前watcher
      while (i--) {
    
    
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

traverse

// 源码目录:src/core/observer/traverse.js

import {
    
     _Set as Set, isObject } from '../util/index'
import type {
    
     SimpleSet } from '../util/index'
import VNode from '../vdom/vnode'

const seenObjects = new Set()

/**
 * Recursively traverse an object to evoke all converted
 * getters, so that every nested property inside the object
 * is collected as a "deep" dependency.
 */
export function traverse (val: any) {
    
    
  _traverse(val, seenObjects)
  seenObjects.clear()
}

// 深度遍历对象的每个属性,每次访问都会触发它的getter,从而收集到依赖,就是订阅它们变化的 watcher。
// 这里有一个小优化是使用集合Set记录依赖收集器的Id,避免重复访问
function _traverse (val: any, seen: SimpleSet) {
    
    
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    
    
    return
  }
  if (val.__ob__) {
    
    
    // 避免重复收集依赖,导致重复触发getter
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
    
    
      return
    }
    seen.add(depId)
  }
  if (isA) {
    
    
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    
    
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

猜你喜欢

转载自blog.csdn.net/sinat_36521655/article/details/109188651