vue2源码学习 (7).响应式原理-5.Dep

110 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 11 天,点击查看活动详情

7.响应式原理-5.Dep

start

说实话看了前面的文章,对 Dep 是什么,非常的好奇,别急现在就来看。

dep.js

\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 {
  static target: ?Watcher // static 静态方法,等同于 `Dep.target`
  id: number // 实例属性 (可以通过this直接访问) (数字类型)
  subs: Array<Watcher> // 实例属性 (可以通过this直接访问) (数组类型,存储的是Watcher)

  constructor() {
    // 唯一id
    this.id = uid++
    // 初始化 subs为一个空数组
    this.subs = []
  }

  // sub subscribing/订阅
  // 添加订阅
  addSub(sub: Watcher) {
    this.subs.push(sub)
  }

  // 删除依赖
  removeSub(sub: Watcher) {
    // remove 利用数组的 splice 删除数组从前到后匹配到的第一项;
    remove(this.subs, sub)
  }

  // 依赖depend
  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不会在调度程序中排序
      // 我们现在需要对它们进行排序,以确保它们正确地发射
      // 订单
      subs.sort((a, b) => a.id - b.id)
    }

    // 核心:遍历subs中的每一项,触发对应的 `update` 方法
    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 = []

// 这里会有一个数组 targetStack ,栈结构 (栈结构,特点:后进先出)

// 向 targetStack 中push数据
export function pushTarget(target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

// 从 targetStack 取出最后一项
export function popTarget() {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

看到上述的代码:

  • Dep 也是一个类;
  • Dep 的实例 dep 中存在一个属性 subs,数组类型,存储着 传入的 ”sub“;
  • dep 上还有一些方法,可以向自身的 subs 属性,添加数据。
  • 收集依赖:把依赖 存放在 subs 中;
  • 通知更新:调用依赖的 update 方法;

注意:Dep.target

Dep 类是唯一的,它自身的 target 也是唯一的。可以通过这个属性来传递数据。

回头再看一看 dep 是如何使用的

1. defineReactive中使用 dep

// 精简版本的defineReactive, 主要目的是查看一下 dep 相关逻辑
export function defineReactive(obj: Object, key: string, val: any) {
  const dep = new Dep()

  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 的。

  1. 首先const dep = new Dep();
  2. 读取数据的时候,如果 Dep.target存在,执行dep.depend();
  3. 设置数据的时候,dep.notify()

    这里的 dep 存储在了哪里?? 答:

    • 内部的 get,set 使用了外部的 dep,形成了闭包,因此 dep 长存于内存中。
    • watcher 也会存储这里的 dep。后续讲 watcher 的时候细说。

Dep中对应的方法

// 依赖depend
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }

  // 通知
  notify() {
    const subs = this.subs.slice();

    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  }

上面的实例代码,Dep.target,subs[i]其实都是存储的 watcher;

watcher 不懂没关系,暂时先理解它就是一个存储信息的对象,后续咱们去看它的源码仔细研究。

2. Observer中使用 dep

export class Observer {
  // 2. class直接定义变量, 相当于 function Observer(){}  ;Observer.value;Observer.dep; Observer.vmCount;
  value: any
  dep: Dep
  vmCount: number // number of vms that have this object as root $data
  constructor(value: any) {
    // 3.存储 value
    this.value = value

    // 4.创建一个依赖收集者
    this.dep = new Dep()
  }
}

Observer实例 上也有Dep的实例=> dep,使用方式:__ob__.dep.notify

思考

  1. 面向对象编程

    看 Vue.js 源码看到这里,说说我个人的思考:

    • 使用 js 实现功能,可以按照步骤,一个功能一行代码。(面相过程编程)
    • 但是可以看到我们的 Vue.js, 例如 Vue Observer Dep都是使用的面相对象的思想。
    • 虽然我们这里的 Dep 本质作用是用来存储数据,但是也使用了一个类的方式来定义。

    面相对象的好处:

    1. 每一个对象都是功能的中心,分工明确;
    2. 灵活,代码可复用,容易维护和开发; 等
  2. Dep.target

    在整个运行环境,Dep 类是唯一的,使用 Dep.target 可以很方便的传递唯一数据,保证数据唯一性,后续有类似的需求,也可以模仿。

end

  • 本文主要阅读了 Dep 的相关源代码。
  • 整体看下来,Dep 并不是什么看不懂的东西,就是一个对象,用来存储数据的对象。
  • 处理存储数据,实例上还有一些方法,用来收集数据,通知数据。