Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 🎸 Vue源码阅读-响应式原理Dep && traverse #149

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!--
* @Author: wangzn
* @Date: 2021-03-14 19:05:29
* @LastEditTime: 2021-05-16 20:17:40
* @LastEditTime: 2021-05-25 19:27:12
* @LastEditors: wangzn
* @Description: 源码阅读-core-Vue构造函数
-->
Expand Down Expand Up @@ -207,7 +207,7 @@
// 初始化 options.methods,method不需要响应式化,仅遍历method,进行属性判重,不与prop重复,并通过bind方法将method绑定至Vue实例(this)上。
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
// 初始化 options.data,
// 初始化 options.data,data初始化放在methods与props之后,可见data初始化时能够访问methods与props
// 在Vue实例中,data为Object,而在组件中,data为Function
// 判重处理,不与prop、method重复
initData(vm)
Expand Down
132 changes: 130 additions & 2 deletions Vue/Vue2.6.x源码阅读/7.源码阅读-core-响应式原理.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!--
* @Author: wangzn
* @Date: 2021-05-16 20:18:21
* @LastEditTime: 2021-05-23 21:21:29
* @LastEditTime: 2021-06-06 20:12:55
* @LastEditors: wangzn
* @Description:
-->
Expand Down Expand Up @@ -117,7 +117,7 @@
// 假设arr中的元素原来均为响应式,那么通过 arr.push(obj) 新加入的obj也需要是响应式的
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify() // 通知调度中心进行派发更新
ob.dep.notify() // 通知调度中心进行派发更新,这也是为什么需要重写数组方法的关键所在
return result // return重写数组方法的结果
})
})
Expand All @@ -127,7 +127,135 @@

### dep.js

* 正如上面所提到的,``Dep(Dependency)``类担任着调度中心的角色,主要工作即为依赖收集与派发更新。``Dep.js``完整的定义类整个``Dep class``。总的来讲``Dep``类的定义还是比较简单且易于理解的,那么首先来看下整个类的代码。

```js
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
static target: ?Watcher; // 当前处理的Watcher
id: number; // Dep唯一标识
subs: Array<Watcher>; // 订阅的Watcher

/* 构造函数 */
constructor () {
this.id = uid++
this.subs = [] // 默认无订阅的Watcher
}

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

/* 移除Watcher订阅 */
removeSub (sub: Watcher) {
remove(this.subs, sub) // remove方法定义于 shared/util 当中
}

/* 依赖收集 */
depend () {
// 由于在Vue实例初始化时不需要做依赖收集,在组件初始化时才需要,所以此处Dep.target进行了判空保护。
if (Dep.target) {
// 当前存在处理的target时,依赖收集 depend 方法完成以下两件事
// 1. 在 target(Watcher) 中添加当前Dep
// 2. addDep 方法中也调用了 Dep 的 addSub 方法将 target 添加到了 Dep 的 subs 列表当中
// 所以依赖收集即是让 Dep 与 Watcher 建立了一个双向的关系
Dep.target.addDep(this)
}
}

/* 派发更新 */
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice() // 深拷贝当前 subs 列表
// ...
// 遍历当前 subs 列表,调用 update 方法通知 Watcher 更新数据
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
```

* 全局下,当前用于建立依赖收集的``Watcher``只能有一个,即``Dep.target``,所以``Dep``中也定义了一个栈(Stack),用``FIFO``模式处理``Watcher``

```js
// 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 = [] // 待处理的 Watcher 栈

export function pushTarget (target: ?Watcher) { // 入参可以为空
targetStack.push(target) // target入栈
Dep.target = target // 优先 evaluate 新入栈的 target
}

export function popTarget () {
targetStack.pop() // target 出栈
Dep.target = targetStack[targetStack.length - 1] // 当前需 evaluate 的 target 变为栈中下一个顶层 target
}
```

### traverse.js

* ``traverse.js``中仅包含了一个``traverse``方法,用于递归遍历数组、对象类型的数据。乍一看这个方法就只是做了递归遍历的操作,除此之外什么也没做。而在调用``_traverse``时,其实也伴随着调用了对象的``getter``,通过遍历触发了依赖收集。

```js
const seenObjects = new Set()

/**
* Recursively traverse an object to evoke all converted
* getters, so that every nested property inside the object
* is collected as a "deep" dependency.
*/
export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear() // 清空Set
}

function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
// 如果不是响应式的数据就不需要递归遍历
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
// __ob__ 表示 val 是响应式对象,ob 即 Observer
if (val.__ob__) {
const depId = val.__ob__.dep.id // 每个 Observer 都会含有一个调度中心 Dep
// 判断 Set 当中是否含有该 id
// 保证循环引用不会递归遍历
if (seen.has(depId)) {
return
}
seen.add(depId) // 无重复 Dep,添加至 Set 当中
}
if (isA) {
// 递归访问数组中的每个元素,查看其中是否含有对象
i = val.length
while (i--) _traverse(val[i], seen)
} else {
// 递归访问对象中的每个属性
keys = Object.keys(val)
i = keys.length
// val[keys[i]] 获取到了对象的属性,调用了getter进行访问,进而触发了依赖收集
while (i--) _traverse(val[keys[i]], seen)
}
}

```

### index.js

* ``observer/index.js``自然为``Observer``类的定义代码,上文提到``Observer``为发布者(被订阅),它与``Data``直接建立联系,将数据转化为响应式的数据。

### watcher.js

### scheduler.js

## 构造函数中的数据初始化

Expand Down