Skip to content

Commit

Permalink
dep/watcher
Browse files Browse the repository at this point in the history
  • Loading branch information
wuyawei committed Apr 7, 2019
1 parent e2d89dd commit b18a584
Showing 1 changed file with 178 additions and 13 deletions.
191 changes: 178 additions & 13 deletions vue/mvvm/1.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ arr[0] = 'oh nanana'; // set
我们知道了数据劫持的基础实现,顺便再看看 Vue 源码是如何做的。
``` javascript
// observer/index.js
// 数据劫持前的判断方法
// Observer 前的预处理方法
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) { // 是否是对象或者虚拟dom
return
Expand Down Expand Up @@ -163,7 +163,7 @@ export class Observer {

constructor (value: any) {
this.value = value
this.dep = new Dep() // 给 Observer 添加 Dep 实例
this.dep = new Dep() // 给 Observer 添加 Dep 实例,用于收集依赖,辅助 vm.$set/数组方法等
this.vmCount = 0
// 为被劫持的对象添加__ob__属性,指向自身 Observer 实例。作为是否 Observer 的唯一标识。
def(value, '__ob__', this)
Expand Down Expand Up @@ -193,7 +193,7 @@ export class Observer {
}
}
```
上面需要注意的是 `__ob__` 属性,避免重复创建。然后 Vue 将对象和数组分开处理,数组只深度监听了对象成员,这也是之前说的导致不能直接操作索引的原因。但是数组的一些方法是可以正常响应的,比如 push、pop 等,这便是因为上述判断响应对象是否是数组时,做的处理,我们来看看具体代码。
上面需要注意的是 `__ob__` 属性,避免重复创建`__ob__`上有一个 dep 属性,作为依赖收集的储存器,在 vm.$set、数组的 push 等多种方法上需要用到。然后 Vue 将对象和数组分开处理,数组只深度监听了对象成员,这也是之前说的导致不能直接操作索引的原因。但是数组的一些方法是可以正常响应的,比如 push、pop 等,这便是因为上述判断响应对象是否是数组时,做的处理,我们来看看具体代码。
``` javascript
// observer/index.js
import { arrayMethods } from './array'
Expand Down Expand Up @@ -268,7 +268,7 @@ methodsToPatch.forEach(function (method) {
}
if (inserted) ob.observeArray(inserted) // 原数组的新增部分需要重新 observe
// notify change
ob.dep.notify() // 手动发布
ob.dep.notify() // 手动发布,利用__ob__ 的 dep 实例
return result
})
})
Expand All @@ -292,20 +292,20 @@ export function defineReactive (
// 兼容预定义的 getter/setter
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
if ((!getter || setter) && arguments.length === 2) { // 初始化 val
val = obj[key]
}

let childOb = !shallow && observe(val) // 需要深度Watch就再次监听val,从 observe 开始,走完一套流程
// 默认监听子对象,从 observe 开始,返回 __ob__ 属性 即 Observer 实例
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val // 执行预设的getter获取值
if (Dep.target) { // 依赖收集的关键
dep.depend() // 依赖收集
if (childOb) { // 如果有深度属性,则添加同样的依赖
childOb.dep.depend()
dep.depend() // 依赖收集,利用了函数闭包的特性
if (childOb) { // 如果有子对象,则添加同样的依赖
childOb.dep.depend() // 即 Observer时的 this.dep = new Dep();
if (Array.isArray(value)) { // value 是数组的话调用数组的方法
dependArray(value)
}
Expand All @@ -316,6 +316,7 @@ export function defineReactive (
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
// 原有值和新值比较,值一样则不做处理
// newVal !== newVal && value !== value 这个比较有意思,但其实是为了处理 NaN
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
Expand All @@ -328,8 +329,8 @@ export function defineReactive (
} else { // 没有预设直接赋值
val = newVal
}
childOb = !shallow && observe(newVal) // 是否要深度监听新设置的值
dep.notify() // 发布
childOb = !shallow && observe(newVal) // 是否要观察新设置的值
dep.notify() // 发布,利用了函数闭包的特性
}
})
}
Expand All @@ -345,7 +346,171 @@ function dependArray (value: Array<any>) {
}
```
#### Dep
在上面的分析中,我们弄懂了 Vue 的数据劫持以及数组方法重写,但是又有了新的疑惑。
在上面的分析中,我们弄懂了 Vue 的数据劫持以及数组方法重写,但是又有了新的疑惑,Dep 是做什么的?Dep 是一个发布者,可以被多个观察者订阅。
``` javascript
// observer/dep.js

let uid = 0
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;

constructor () {
this.id = uid++ // 唯一id
this.subs = [] // 观察者集合
}
// 添加观察者
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除观察者
removeSub (sub: Watcher) {
remove(this.subs, sub)
}

depend () { // 核心,如果存在 Dep.target,则进行依赖收集操作
if (Dep.target) {
Dep.target.addDep(this)
}
}

notify () {
const subs = this.subs.slice() // 避免污染原来的集合
// 如果不是异步执行,先进行排序,保证观察者执行顺序
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 发布执行
}
}
}

Dep.target = null // 核心,用于闭包时,保存特定的值
const targetStack = []
// 给 Dep.target 赋值当前Watcher,并添加进target栈
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
// 移除最后一个Watcher,并将剩余target栈的最后一个赋值给 Dep.target
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
```
#### Watcher
单个看 Dep 可能不太好理解,我们结合 Watcher 一起分析。
``` javascript
// observer/watcher.js

let uid = 0
export default class Watcher {
// ...
constructor (
vm: Component, // 组件实例对象
expOrFn: string | Function, // 要观察的表达式,函数,或者字符串,只要能触发取值操作
cb: Function, // 被观察者发生变化后的回调
options?: ?Object, // 参数
isRenderWatcher?: boolean // 是否是渲染函数的观察者
) {
this.vm = vm // Watcher有一个 vm 属性,表明它是属于哪个组件的
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this) // 给组件实例的_watchers属性添加观察者实例
// options
if (options) {
this.deep = !!options.deep // 深度
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync // 同步执行
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb // 回调
this.id = ++uid // uid for batching // 唯一标识
this.active = true // 观察者实例是否激活
this.dirty = this.lazy // for lazy watchers
// 避免依赖重复收集的处理
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()

this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else { // 类似于 Obj.a 的字符串
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
)
}
}
this.value = this.lazy
? undefined
: this.get()
}

get () { // 触发取值操作,进而触发属性的getter
pushTarget(this) // Dep 中提到的:给 Dep.target 赋值
let value
const vm = this.vm
try {
// 核心,运行观察者表达式,进行取值,触发getter,从而在闭包中添加watcher
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) { // 如果要深度监测,再对 value 执行操作
traverse(value)
}
// 清理依赖收集
popTarget()
this.cleanupDeps()
}
return value
}

addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) { // 避免依赖重复收集
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this) // dep 添加订阅者
}
}
}

update () { // 更新
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run() // 同步直接运行
} else { // 否则加入异步队列等待执行
queueWatcher(this)
}
}
}
```
到这里,我们可以大概总结一些整个响应式系统的流程:第一步当然是通过 observer 进行数据劫持,然后在需要订阅的地方(如:模版编译),添加观察者(watcher),并立刻通过取值操作触发指定属性的 getter 方法,从而将观察者添加进 Dep (利用了闭包的特性),然后在 Setter 触发的时候,进行 notify,通知给所有观察者进行相应的 update。
## Proxy
## MVVM
### 概念

0 comments on commit b18a584

Please sign in to comment.