深入 Vue 响应式原理
发布于 6 年前
约 2 分钟阅读
在 Vue 3 之前,Vue 使用了 Object.defineProperty()
来实现响应式。
什么是响应式?
简单来说就是当数据发生修改时,视图会进行相应的更新。
观察者模式
在分析 Object.defineProperty()
的 API 之前,我们先来看下观察者模式,这样对响应式中的依赖收集,分发有个感性的认知。
观察者模式在前端开发中很常见,例如事件模型。当我们给 DOM 对象添加一个事件监听器时,DOM 对象就增加了一个新的依赖,这个过程就被称为依赖收集。当触发 DOM 的事件时,所有相关的事件监听器都会被执行,这个过程就被称为事件分发。
下面是一个简单的事件模型。
class Target {
constructor () {
this.listener = {} // 监听器列表
}
/**
* 添加监听器到监听器列表
* @param event 事件名称
* @param fn 监听器
*/
addListener (event,fn){
if(!this.listener[event]){
this.listener[event] = []
}
this.listener[event].push(fn)
}
/**
* 分发事件
*/
dispatch () {
let event = Array.prototype.shift.call(arguments)
let listeners = this.listener[event]
if(!listeners || listeners.length ===0)
return
listeners.forEach((fn)=>{
fn.apply(this,arguments)
})
}
remove (event,fn){
let fns = this.listener[event]
if (!fns) {
return false
}else if (!fn) {
fns.length = 0
}else {
for(let i = fns.length-1; i>=0;i--){
let _fn = fns[i];
if(_fn === fn){
fns.splice(i,1)
}
}
}
}
}
let target = new Target()
target.addListener('click',(info)=> {
console.log(info)
})
target.dispatch('click','foo')
target.remove('click')
Object.defineProperty()
Object.defineProperty()
中很重要的两个概念就是 getter 和 setter。当属性值修改时,触发 setter。当访问属性值时,触发 getter。相当于属性值的两个生命周期函数。
当视图渲染的时候,会读取属性值,也就是读取 setter 的返回值,我们可以在这个时候收集依赖。当属性值修改时,会触发 setter,这个时候可以进行依赖分发。
Vue 官方文档提供的图很清晰地展示了这个过程。
源码分析
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// ...
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
// ...
})
}
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
}
export default class Watcher {
// ...
get () {
pushTarget(this)
let value
const vm = this.vm
try {
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)
}
popTarget()
this.cleanupDeps()
}
return value
}
// ...
}
Vue 中的 Watcher 类就是观察者模式中的观察者,主要主责视图更新。负责存储观察者列表的类为 Dep。
当视图更新时,调用 Dep 中的 depend 方法将 Watcher 添加到依赖列表。当数据修改时,会触发 this.getter.call(vm, vm)
,this.getter
对应就是 updateComponent
函数,会触发视图更新。
参考资料
- 什么是响应式?
- 观察者模式
- Object.defineProperty()
- 源码分析
- 参考资料
除特别注明外,所有文章均采用 Creative Commons BY-NC-ND 4.0(自由转载-保持署名-非商用-禁止演绎)协议 发布