直接上图
按图索骥
- 首先找到响应是的入口
- 找到源码中的入口
/core/instance/state.js
, 就是在数据初始化时执行的 initData, 在该方法中发现了,执行了observer(data)
方法,这个就是入口了
- 找到源码中的入口
- 然后看看
observer(data)
干了什么,在/core/observer/index.js
- 首先干了一些判断的事儿,是不是对象了、是不是vnode 对象等等,直接跳过
- 重要的是:实例化了一个 Observer 对象
- 接着,看看这个实例构造函数
Observer
是什么东西了- 发现对于对象的情况,执行了一个 walk 方法
- 对于每一个数据都 执行了一个
defineReactive
, 终于找到关键地方了
- 看看
defineReactive
方法- 咱只看关键部分,细节不看了(太多了)
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 在这里收集依赖 if (Dep.target) { dep.depend() } }, set() { // 在这里触发依赖更新 dep.notify() } })
- 完了,说不下去了
接着看看图吧😂, 按照图的序号接着说
先说绿色的收集依赖
-
创建 watcher 实例,watcher 是啥玩意儿,看起来就是用到数据的地方,就这样理解吧
-
执行 watcher 的 get 方法,通过 Dep.target = this 把 Dep.target 赋值为当前new 出来的 watcher 实例
-
执行 Dep.append 进行依赖收集,看 dep 里面是干了什么
depend () { if (Dep.target) { // 刚刚实例化 watcher 时不是把 Dep.target 赋值成了 watcher 实例吗 // 所以执行watcher上的addDep方法, 并把当前 dep 实例作为参数传了过去 Dep.target.addDep(this) } }
-
进入到 Watcher 构造函数中看看,执行addDep方法
// 有一点开始晕了, addDep(dep) { dep.addSub(this) }
-
发现又回到 dep 中了,执行其addSub方法, 把 watcher 放入到定义好的 sub 列表中 看到这儿有一点晕了,收集依赖,首先是 dep 执行 append 方法,然后去调用 watcher 的 addDep 方法,而 watcher,而addDep 方法 又调用了 Dep 方法从而实现了把 watcher 放入到 sub 列表中,真的是妙啊 整个流转: dep.append --> watcher.addDep(dep) --> dep.addSub(watcher); 最终实现了吧 watcher 收集在 dep 中
再说蓝色的触发更新
- dep.notify , 执行 dep 中的更新方法
遍历 之前收集到的所有用到该变量的地方,调用更新方法
notify() { for (let i = 0, l = subs.length; i < l; i++) { // 执行每个watcher中的update方法,更新数据 subs[i].update() } }
- 执行 watcher 实例的 update 方法,
- 把更改放入队列中 queueWatcher,等待更新
数组的更新
由于Obejct.defineProperty 方法不能读取到数组的变化,所以我们的目的解决如果知道数组发生了变化, 这样我们可以去做相应的更新视图操作, 看一下,对源码中分开的方法大概整合到了一起
if (Array.isArray(value)) {
// 判断是否有__proto__属性
if (hasProto) {
// 覆盖掉 数组原来的原型,以便实现数组的响应式
value.__proto__ = arrayMethods
} else {
// 如果 没有 __proto__ 属性,就说明没有原型,就直接把修改过后的数据直接更改到数组自身
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
value.key = arrayMethods[key]
}
}
// 这个是如果数组元素是对象的监听这个对象
this.observeArray(value)
}
可以看到对于数组的方法有两种处理方法,如果有原型就覆盖原型链上的方法,如果没有的话直接把当前方法修改为拦截方法,下面,就看一下具体的拦截原型的方法。
再次强调,监听数组的方法,目的是知道数组发生了变化,我们可以去作相应更新 因为我们的目的是知道数组发生了改变,所以只复写可以更改数组的七个方法,如下
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
// 这里的 def 就是 Object.defineProperty 方法,拦截了数组的操作,比如说 之前的 arr.push
// 现在就不会走原始原型链上的方法,就会走下面的 mutator 方法
def(arrayMethods, method, function mutator (...args) {
// 这个是执行一个原始的数组方法
const result = original.apply(this, args)
// 这个 ob 是在 observe 时,把 observer 实例 放到了 数组value上的
// 所以可以在push 方法里拿到 observer。
const ob = this.__ob__
// 这里,拿到插入值,是如果插入的元素是对象类型,对新增的元素作响应式处理
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// observeArray 方法是给 数组的对象元素作响应式
if (inserted) ob.observeArray(inserted)
// notify change
// 这个是数组响应式的关键
// 因为执行了数组的方法,会走到这里
// 然后我们去执行dep 的更新操作
// 就是 之前 dep.notify --> watcher.update() --> queueWatcher(watcher)
ob.dep.notify()
return result
})
})
这差不多就是响应式的基本内容了,刚开始梳理,有问题的地方,还烦请路过大佬 批评指正。
这里附上 vue 源码连接 vue源码