Vue2源码学习笔记 - 12.响应式原理—Dep 类详解

我们继续学习 Dep 类,这个类在响应式中也是很重要的类,在实例化 Observer 类对象的时候就创建了一个 Dep 实例对象保存在 Observer 对象的成员变量中,这就是一对一的关系,也就是说 ob 与 Dep 实例是一对一关联的,文章配有大量生动形象的图,不要慌。我们先看下它的 UML 类图结构

UML

vue Dep类uml

数据结构——栈

在研究 Dep 的代码之前我们先来简单学习下一种数据结构——栈,它是一种线性存储结构,规定一端为栈底,一端为栈顶,每次插入(入栈或压栈)与删除(出栈或弹栈)操作都只能在栈顶操作。栈中的数据元素遵守“后进先出”的原则,也可以说是“先进后出"(First In Last Out)的原则。栈的实现有基于链表的,也有基于数组的,也还有其他的,这个不同语言根据不同需求实现即可。

数据结构-栈

使用场景

Dep 的实例化场景只有两个,一个是在 Observer 类的构造函数中,另一个是在 defineReactive 函数中。

1、Observer 类构造函数中
export class Observer {
  ...
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()  // <----
    this.vmCount = 0
    ...
  }
}

在 Observer 构造函数中实例化了一个 Dep 对象,并赋值给成员变量 dep,这样 Observer 对象就与 Dep 对象一对一关联起来。

2、 defineReactive 函数中
export function defineReactive (
  obj: Object, key: string, val: any,
  customSetter?: ?Function, shallow?: boolean
) {
  const dep = new Dep()  // <----
  const property = Object.getOwnPropertyDescriptor(obj, key)
  const getter = property && property.get
  ...
  Object.defineProperty(obj, key, {
    ...
    get: function reactiveGetter () { 
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        ...
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      ...
      dep.notify()
    }
  })
}

在 defineReactive 函数中实例化了一个 Dep 对象,并赋值给了局部变量 dep,在用 Object.defineProperty 定义的响应式属性的 reactiveGetter\reactiveSetter 方法中引用了这个局部变量 dep,这是利用闭包来保存属性的相关信息,其他还有比如 val、obj 和 getter\setter 等。

经过 defineReactive 处理后的对象属性具备有响应式功能,且每个属性通过在闭包中保存了一份 Dep 对象及其他必要信息,最后,对象的属性与 Dep 对象同样也实现了一对一的关联。如下图所示:

vue observe与defineReactive 响应式处理流程

Dep

现在我们来学习 Dep 类,先看源码,它在文件 /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.
 */
export default class Dep {
  // 静态变量,保存 Watcher 类型对象
  static target: ?Watcher;
  id: number;               // 对象的ID
  subs: Array<Watcher>;     // 订阅者数组 元素即 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()
    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 = null       // 全局静态变量初始赋值
const targetStack = []  // 定义 Watcher 数组,用于实现 栈结构

// 入栈 Watcher 对象
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

// 出栈 Watcher 对象
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

pushTarget 与 popTarget 函数

Dep 除了成员变量和方法外,还定义了一个全局唯一用于存取 Watcher 对象的静态变量 Dep.target 以及一个用于实现栈结构的数组 targetStack。还有两个全局函数 pushTarget 和 popTarget,pushTarget 的作用是往 targetStack 数组尾部压入(保存) Watcher 对象,并把对象赋值给 Dep.target;popTarget 的作用是把 targetStack 数组尾部最后一个 Watcher 对象弹出(删除),然后把数组最后一个对象赋值给 Dep.target。

汇编-栈

如果熟悉汇编语言我们就很容易理解,Vue 中这个 pushTarget 和 popTarget 其实很类似于汇编中的 push 和 pop 指令,还有 call 和 ret 指令也是隐式使用了出入栈,他们都是利用栈来临时保存关键信息以便必要时快捷地恢复。当调用 pushTarget 传入值时,Dep.target 用于依赖收集,当调用 pushTarget 传入空值时,依赖收集将被禁用。popTarget 则用于恢复原来的 Dep.target 状态,这个我们后面会继续研究学习。

vue Dep 依赖收集

addSub 与 removeSub 方法

再来看看 Dep 的成员方法,它们都比较简单。一个 addSub 用于添加依赖此变量的 Watcher 对象,把它们存入成员数组变量 subs 中。一个 removeSub 用于删除依赖此变量的 Watcher 对象,即从成员数组变量 subs 中删除特定 Watcher 对象。

vue Dep与 Watcher 依赖关联

depend 方法

方法 depend 是一个用于依赖收集的方法,它在响应式属性被引用时执行,即在属性的 reactiveGetter\reactiveSetter 方法中执行。它会判断如果存在 Dep.target 全局唯一静态变量(值为 Watcher 对象)那么就调用 Dep.target 的 addDep 方法,参数为本 Dep 对象,Dep.target 就是 Watcher 对象,所以 Dep.target.addDep(this) 可以简单理解为 watcher.addDep(dep)。

notify 方法

最后是 notify 方法,它用来通知订阅的所有 Watcher 对象,即调用 addSub 时添加的对象。这是通过变量数组 subs 调用每个元素的 update 方法实现通知的,具体我们在研究学习 Watcher 类和章节串联时具体再看。

总结:

Dep 类对于整个响应式功能模块是非常重要的,它与要实现响应式功能的数据对象和对象属性关联,它只在 Observer 类构造函数和 defineReactive 函数中实例化,并在 setter\getter 方法中的特定场景下执行依赖收集(执行 Dep.depend 方法)和派发更新通知(执行 Dep.notify 方法)。Dep 类代码量不大但很重要,它与其他类和模块配合完成响应式的功能,后面我们还会继续研究学习它。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值