目录
1:前言
响应式系统式vue框架的核心内容之一,涉及到的知识点和源码部分比较多,这里目的是对主流程做一个梳理。建立起有关响应式的知识体系,细节的地方应该需要不断重复体会!
所以分享的思路是注重于流程介绍,相关函数仅作介绍。
2:什么是响应式
被观测的数据在改动时会通知视图的重新渲染,也就是model->view的流程,那么在vue中常见的被观测数据有哪些呢
- data
- props
- computed
- watch/method(和前面数据有关)
那么问题在与如何做到修改这些数据会触发视图更新?
3:主要流程
- 数据监听
- 依赖收集/派发更新
- 观察者模式
3.1:数据监听
实现监听主要是做了什么?
使得我们可以在对一个响应式数据在访问,赋值的时候加以处理
实现对data里的数据观测,相关函数如下
- observe(data)
- defineReactive(obj,key)
- Object.defineProperty
vue里的data出于作用域考虑设计成一个函数,这里是的data是最后的返回值为对象,observe会遍历data的每一个值,并递归处理存在嵌套的对象
/*
模拟实现 observe 函数
*/
function observe (data) {
let dep = new Dep() // 这是用来收集watcher的订阅器,在闭包中被get,set所引用
// 需要保证提供的data是一个对象
if (!data || typeof data !== 'object') return
// 遍历data数据,逐一处理
Object.keys(data).forEach((item) => {
// 递归处理
if (typeof item === 'object') {
observe(item)
} else {
defineReactive(data,item,data[item])
}
})
}
function defineReactive (obj,key,value) {
Object.defineProperty(obj,key,{
enumerable: true, // 可枚举,可以遍历访问
configurable: true, // 可以删除
get: function reactiveGetter () {
/*
收集依赖,返回值
*/
console.log('collect')
return value
},
set: function reactiveSetter () {
/*
触发依赖
*/
dep.notify()
}
})
}
3.1.1:存在问题
基于Object.defineProperty实现的数据观测存在一些问题
- api层面无法监听数对象的增加删除,仅能处理值的改变
- 无法监听数组变化,所以需要基于Object.create(Array.prototype)修改数组方法,主要是变异数组方法
- 数组的下标访问修改,或者长度修改无法监听
- 基于proxy对数据监听加以改进(待处理)
3.2:依赖收集/派发更新
3.2.1实现流程
前面我们大致提到在getter中会收集依赖,下面就来具体看看这个流程怎么实现的
- 在数据观测的时候我们看到有一个let dep = new Dep() ,这就是订阅者
- 对于每一个数据都有自己的dep实例
- watcher是观察者,它是一个用到了data数据的表达式或者函数
// dep和watcher是如何构成观察者模式的
class Dep {
constructor () {
// 这是用来保存watcher的数组
this.subs = []
}
addSubs (sub) {
this.subs.push(sub)
},
notify () {
this.subs.forEach((sub) => {
sub.update()
})
}
}
这里再回顾一下
- 当触发get的时候,watcher被收集
- 当set触发的时候,所有watcher都要更新
需要明确一个概念,这里的dep--watcher之间建立的是观察者模式,而不是发布订阅模式!
在很多文章中都会看到一句话“观察者模式(observe)或者叫做发布订阅模式(pub/sub)”,这两者是有区别的,读者在看到类似的文章需要有意识识别!
这个知识点不是本文的重点,简单列举一二
- pub/sub模式是基于observe的解耦,多了一层调度中心的概念
- observe无法控制通知哪些订阅者,只能全部通知
- 可以看到在触发更新的时候,watcher是无差别都要更新的,但是如果是pub/sub的实现可以决定哪些watcher更新
3.2.2:watcher实现
前面只是介绍了有watcher这个观察者,还需要了解一下大致实现
watcher: 是一个用到了data数据的表达式或者函数,在getter中收集,在setter中逐一触发
class Watcher {
constructor(obj, key, cb) {
// Dep.target 获取watcher实例
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
// 这里触发getter
this.value = obj[key]
// 这是一个闭包变量,需要清空
Dep.target = null
}
update() {
// 获得新值
this.value = this.obj[this.key]
// 我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图
this.cb(this.value)
}
}
回顾一下流程
- 在代码中通过表达式/函数操作data值,也就是触发了watcher,在watcher中触发了getter,从而收集当前的watcher
-
b = obj.a 这个操作加入了watcher的这个概念,本来我们认为这只是触发a的getter,b的setter
// 这里再看看响应式的实现 function defineReactive (obj, key, value) { let dp = new Dep() //一个闭包变量,作为观察者 Object.defineProperty(obj, key, { enumerable: true, //可枚举(可以遍历) configurable: true, //可配置(比如可以删除) get: function reactiveGetter () { console.log('get', value) // 监听 // 将 Watcher 添加到订阅 if (Dep.target) { dp.addSub(Dep.target) // 新增 } return value }, set: function reactiveSetter (newVal) { observe(newVal) //如果赋值是一个对象,也要递归子属性 if (newVal !== value) { console.log('set', newVal) // 监听 render() value = newVal // 执行 watcher 的 update 方法 dp.notify() //新增 } } }) }
此时还剩一个地方,什么促成了watcher的触发,答案是render。
4:回顾与总结
- data的数据经过observe处理重写getter/setter
- 在render的时候通知watcher,触发getter收集依赖
- 每一个数据的dep,所有的watcher构成了观察者模式
- 当setter触发时,通知所有watcher触发更新,再执行render更新virtual-dom
- 而virtual-dom到视图的过程,会有一个patch/mount的过程,属于虚拟dom的概念,不在此介绍
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。