持续创作,加速成长!这是我参与「掘金日新计划 · 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 的。
- 首先
const dep = new Dep(); - 读取数据的时候,如果
Dep.target存在,执行dep.depend(); - 设置数据的时候,
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。
思考
-
面向对象编程
看 Vue.js 源码看到这里,说说我个人的思考:
- 使用 js 实现功能,可以按照步骤,一个功能一行代码。(面相过程编程)
- 但是可以看到我们的 Vue.js, 例如
Vue Observer Dep都是使用的面相对象的思想。 - 虽然我们这里的 Dep 本质作用是用来存储数据,但是也使用了一个类的方式来定义。
面相对象的好处:
- 每一个对象都是功能的中心,分工明确;
- 灵活,代码可复用,容易维护和开发; 等
-
Dep.target在整个运行环境,Dep 类是唯一的,使用
Dep.target可以很方便的传递唯一数据,保证数据唯一性,后续有类似的需求,也可以模仿。
end
- 本文主要阅读了 Dep 的相关源代码。
- 整体看下来,Dep 并不是什么看不懂的东西,就是一个对象,用来存储数据的对象。
- 处理存储数据,实例上还有一些方法,用来收集数据,通知数据。