/* @flow */
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
let uid = 0
/**
* dep是一个可观察的,可以有多个订阅它的指令
*/
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<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 () {
// 首先确定订阅列表
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs如果不是异步执行的就不是按顺序的
// 我们需要对他们排序,以确保它们以正确的顺序触发
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// 当前正在评估的观察目标
// 这是全局唯一的target,因为一个时刻内,
// 就只会有一个观察者函数在执行
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
Dep类
先来聊聊观察者模式:
观察者模式是一种实现一对多关系解耦的行为设计模式。它主要涉及两个角色:观察目标
、观察者
。
观察者要直接订阅观察目标,观察目标一做出通知,观察者就要进行处理
Vue源码中实现依赖收集,实现了三个类:
- Dep:扮演观察目标的角色,每一个数据都会有Dep类实例,它内部有个subs队列,subs就是subscribers的意思,收集依赖数据的观察者,当数据变更时,调用dep.notify()通知观察者 。
- Watcher:扮演观察者的角色,进行观察者函数的包装处理。
- Observer:辅助的可观测类,数组/对象通过它的转化,可成为可观测数据。
Dep类就是用来收集和触发依赖的地方,所有的依赖都放在subs队列里。
收集依赖
addSub (sub: Watcher) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
之前,在之前的defineReactive方法的在getter里的时候,会触发dep.depend()方法
这就是把target的依赖放进subs队列里,存储起来
调用depend()的时候,不直接把Dep.target加入dep.subs,而是调用了Dep.target.addDep ,为了保证dep.subs里的每个watcher都是唯一,所以才不直接把当前watcher塞入dep.subs里。所以depend方法其实就是调用了watcher里的addDep方法。
触发依赖
notify () {
// 首先确定订阅列表
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs如果不是异步执行的就不是按顺序的
// 我们需要对他们排序,以确保它们以正确的顺序触发
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
setter里执行完成值变更后,通知watcher进行更新。setter能在父级里访问到dep,所以就能获得dep.subs,就知道有哪些watcher依赖于当前数据,值更新后,通过调用dep.notify(),来遍历dep.subs里的watcher,执行每个watcher的update()方法,让每个watcher进行更新。
Dep.target
JavaScript是单线程,在vue中,虽然有多个观察者函数(watcher),但是一个时刻内,就只会有一个观察者函数在执行,那么此刻正在执行的那个观察者函数,所对应的Watcher实例,便会被赋给Dep.target这一类变量,从而只要访问Dep.target就能知道当前的观察者是谁。
// 当前正在评估的观察目标
// 这是全局唯一的target,因为一个时刻内,
// 就只会有一个观察者函数在执行
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}