持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 12 天,点击查看活动详情
8.响应式原理-6.Watcher
start
- 前面一直提到的 Watcher ,现在来学习学习。
Watcher 的分类
其实 Watcher 的分类有很多,例如:
- 渲染 Watcher;
- 用户定义的 Watcher;
- 计算属性 Watcher
这里就是列举一下 Watcher 的分类,其实本质都是 Watcher,不要看到很多种类,就觉得复杂。 具体种类的 Watcher 后续会去学习。 页面开始展示的时候,初始化的就是渲染 Watcher,我们就以渲染 Watcher,作为我们学习 Watcher 的起点。
- 利用 vue-cli 搭建的项目,
main.js是这样开始初始化的。
new Vue({
render: (h) => h(App),
}).$mount('#app')
$mount方法是这样的
// \src\platforms\web\runtime\index.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 处理挂载元素
el = el && inBrowser ? query(el) : undefined
// 主要是执行了 mountComponent
return mountComponent(this, el, hydrating)
}
mountComponent的逻辑
具体到 mountComponent 函数的逻辑,暂时先看 new Watcher()
\src\core\instance\lifecycle.js
// 挂载组件
export function mountComponent(
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
let updateComponent
// 更新组件
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
},
},
true
)
}
整个挂载组件做了那些操作。
-
$mount基础功能是 调用了mountComponent; -
mountComponent中new Watcher;- 传入了
updateComponent一个箭头函数。
- Watcher`
// `\src\core\observer\watcher.js`
let uid = 0
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
* 观察者解析表达式,收集依赖项,
* 并在表达式值更改时触发回调。
* 这用于$watch() api和指令。
*/
// 1. 整体看下来,Watcher 其实就是一个类
export default class Watcher {
// 2. 实例上有一堆属性 , vm 是我们的Vue实例
vm: Component // 实例
expression: string // 表达式
cb: Function // 回调函数
id: number // 唯一id
deep: boolean // 是否深度监听
user: boolean // 是否用户自定义的watcher
lazy: boolean // 是否是 lazy Watcher
sync: boolean // 是否同步
dirty: boolean //
active: boolean
deps: Array<Dep> // 存储 旧dep 的数组
newDeps: Array<Dep> // 存储 新dep 的数组
depIds: SimpleSet // 存储 旧dep的id (Set类型,支持去重)
newDepIds: SimpleSet // 存储 新dep 的id (Set类型,支持去重)
before: ?Function
getter: Function
value: any
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
// 3. Watcher实例上存储一下我们的 vm
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
// 4. 一个组件的watch,除了渲染watcher,还有其他的watcher,定义一个变量`_watchers`, 存储所有的watcher。
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
// 全部为false
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching 存储唯一的id
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = [] //存放dep的容器
this.newDeps = [] // 存储新dep的容器
this.depIds = new Set() //去重dep
this.newDepIds = new Set() // 去重 新dep容器
this.expression =
process.env.NODE_ENV !== 'production' ? expOrFn.toString() : ''
// parse expression for getter
if (typeof expOrFn === 'function') {
// expOrFn是我们传入的第二个参数,可以为字符串或函数。
// 如果传入的是函数,函数中使用到的数据都会被观察,只要有一个数据改变,watcher就会收到通知。
// computed就是这么一个原理。 其次仔细想想 正常的渲染watcher,getter是去触发 render,render获取数据。这种传入函数的watcher,getter会去获取函数中使用的数据。
this.getter = expOrFn
} else {
// parsePath解析我们的表达式
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
)
}
}
// 5. 整体看下来上方的逻辑,都是在 watcher 实例上,初始化一些属性;
// 6. 注意这里: this.lazy ,渲染的 watcher的 this.lazy 为 false,会执行 `this.get()`
this.value = this.lazy ? undefined : this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
* 求值getter,并重新收集依赖项。
*/
get() {
// 7. pushTarget会做这么两个操作。
// 7.1 向一个数组中添加当前的 watcher实例;
// 7.2 `Dep.target = this` => `Dep.target = 当前的 watcher`
pushTarget(this)
let value
const vm = this.vm
try {
// 8.
/*
1.执行 this.getter
2.渲染的时候,实际执行的是: () => {vm._update(vm._render(), hydrating);};
3. vm._render()会获取 data 的值,触发了数据的 get ,从而实现依赖收集
*/
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
// “触摸”每个属性,因此它们都被跟踪为
// 依赖深度观察
if (this.deep) {
traverse(value)
}
// 9. 收集完依赖之后,删除当前依赖
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
* 添加新的依赖到当前的指令
*/
addDep(dep: Dep) {
// 10 获取到 dep实例的id
const id = dep.id
// 11. 如果新 depid数组没有这个id
if (!this.newDepIds.has(id)) {
// 添加这个id
this.newDepIds.add(id) // 去重后的 dep id的数 Set (新)
this.newDeps.push(dep) // 存储dep的容器 (新)
// 如果 去重的数组 (旧) 也没有这个 dep.id, dep中收集一下当前的 watcher
if (!this.depIds.has(id)) {
// 收集这个 watcher
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
* 清理依赖项收集。
*/
// 感觉是清除旧的依赖
cleanupDeps() {
let i = this.deps.length // 存储 deps
while (i--) {
const dep = this.deps[i]
// 如果新的newDepIds没有这个 dep.id
if (!this.newDepIds.has(dep.id)) {
// dep中删除当前 watcher
dep.removeSub(this)
}
}
// 1. 设置this.depIds存储的最新id的Set ;2. 清除原本的存储id的Set
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
// 上面替换的是存储id的 Set(Set特性不会重复)
// 这里的作用 `this.deps`更新为最新dep的数组的引用地址,原本旧的数组引用给释放掉。
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 {
// 主要是执行 queueWatcher
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
* 调度器的工作界面。
* 将被调度程序调用。
*/
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) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(
this.cb,
this.vm,
[value, oldValue],
this.vm,
info
)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
* 评估观察者的价值。
* 这只适用于懒惰的观察者。
*/
evaluate() {
// 执行get,重新计算值
this.value = this.get()
// 设置 diety 为 false
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
* 依赖于这个观察者收集的所有深度。
*/
depend() {
// this.deps属性保存了所有状态的dep实例,每个dep实例保存了它的所有依赖
// 简单来说,就是遍历了 this.deps, 将当前的 watcher放到所有的依赖项中。
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
// 从所有依赖项的订阅者列表中删除 自身
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.
// 从vm的监视列表中删除self
// 这是一个有点昂贵的操作,所以我们跳过它
// 如果虚拟机正在被销毁。
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
// this.deps => watcher本身记录着它自己订阅了谁
// 当需要
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
梳理一下Watcher逻辑,说说我个人理解。
-
Watcher 本质是一个类。
-
基于这个类生成的实例也有很多属性;
-
注意 new Watcher 时传入的参数。
vm: Component, // 实例 expOrFn: string | Function, // 表达式 cb: Function, // 回调函数 options?: ?Object, // 配置 isRenderWatcher?: boolean // -
在
new Watcher的时候,如果this.lazy不存在,则会调用 get 方法。 -
Watcher 中的 get 方法
-
纵观全局, get 中主要做了这些操作
Dep.target = 当前的 watcher;- 触发配置项 data 中的属性 get() 方法;
- 置空
Dep.target;
-
详细解释
-
Dep.target 上存储的是我们当前的 watcher 实例。
-
整体的执行逻辑:
this.getter.call(vm, vm)=>vm._render()=>defineReactive中的get=>dep.depend()=>Dep.target.addDep(this);=> 其实就是调用 当前 watcher 中的addDep()
/**
* Add a dependency to this directive.
* 添加新的依赖到当前的指令
*/
addDep(dep: Dep) {
// 10 获取到 dep实例的id
const id = dep.id;
// 11. 如果新 depid 数组没有这个id
if (!this.newDepIds.has(id)) {
// 添加这个id
this.newDepIds.add(id); // 去重后的 dep id的数 Set (新)
this.newDeps.push(dep); // 存储dep的容器 (新)
// 如果 去重的数组 (旧) 也没有这个 dep.id, dep中收集一下当前的 watcher
if (!this.depIds.has(id)) {
// 收集这个 watcher
dep.addSub(this);
}
}
}
-
可以看到,这里的逻辑: Watcher 中的 newDepIds 存储的 dep 的 id; dep 的 subs 存储了当前的 watcher; 两者本身是相互存储。
-
其他就是 watcher 中的一些方法。
思考
pushTarget和popTarget
在收集依赖的时候,经常会看到这两个函数,研究一下。
// 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.target中存储的数据。 -
为什么会有
targetStack呢?设想一个逻辑吧,同样是渲染 watcher。我组件 a 中有组件 b 先渲染 a。此时
Dep.target = a 的 watcher; 渲染到 b 。此时Dep.target = b 的 watcher; b 渲染完毕,继续渲染 a,此时Dep.target已经被清空了; 所以这里利用targetStack栈结构,对前置 watcher 进行了存储,后进先出。
看到网上有人这样解释,targetStack 是 vue2 才引入的。
vue1 视图更新采用的是细粒度绑定的方式,而 vue2 采取的是 virtual DOM 的方式。
两者渲染方式不同,后者会产生嵌套调用的相关逻辑,所以需要引入targetStack
回答的原文
- traverse
首先捋一下 traverse 的调用逻辑,每次执行 watcher 中的 get 的时候,且 depp 存在的时候,会执行 traverse(value)。
finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
// “触摸”每个属性,因此它们都被跟踪为
// 依赖深度观察
if (this.deep) {
traverse(value);
}
}
这个 value 是什么?
// 例如我是这样使用的Vue.js
new Vue({
el: '#app',
template: `
<div id='app'>
<div>a的值: {{a.b}}</div>
</div>
`,
data() {
return {
a: {
b: 1,
},
}
},
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
},
})
此时有两个 Watcher,一个是渲染整个页面的 watcher。一个是通过 watch 配置定义的一个 Watcher。
- 渲染 watcher 传入的 expOrFn 是一个箭头函数 updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 没有返回值,此时value是 undefined
- 自定义的 watcher
传入的 expOrFn 是
'a',通过parsePath进行转换,返回一个函数,函数的返回值就是{ b : 1 }
简单来说,value 就是我们 data 对应表达式的值,如果 deep 配置存在,需要深层监听表达式对应的值,通过 traverse 深层遍历,每个属性都访问一遍,就能够达到收集依赖的目的。
traverse实现的源码
function _traverse(val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
// 不是数组 不是对象 ; 冻结的对象; 是虚拟dom; 直接退出
if (
(!isA && !isObject(val)) ||
Object.isFrozen(val) ||
val instanceof VNode
) {
return
}
// 存在响应式,存储依赖的唯一标识 id
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
// 添加我们的依赖id
seen.add(depId)
}
/* 核心逻辑就是这里,遍历所有的数据项,实现深层数据的依赖收集。 */
// 如果是数组,循环数组,每一项都调用 _traverse
if (isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
// 如果对象 循环 Object中所有的key,执行一次读取,再递归子值。 其实就是这里的dep还没有被清除,这里读取了深层对象,触发了对应的get (递归调用,相当于,底层的对象的dep中都存储了我们这个 watcher)
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
end
- 本节主要阅读了 Watcher 的源码。