本文深入解析 Vue 2 响应式系统的核心 Observer 类,通过源码还原数据劫持与依赖收集的实现过程。重点讲解 observe 方法如何区分数组与对象,以及 defineReactive 如何利用 Object.defineProperty 递归为属性添加 getter/setter,从而实现数据变更时的自动更新与 Watcher 依赖收集。
Vue 2 响应式原理:Observer 源码深度解析
导语:
本文基于对 Vue 官方文档深入响应式原理(https://cn.vuejs.org/v2/guide/reactivity.html)的理解,并通过源码还原实现过程。
Observer 核心职责与依赖收集
Vue 的响应式机制主要包含两个核心阶段:依赖收集与触发更新并重渲染。在这一机制中,Watcher、Dep 和 Observer 是三个关键类。本文将重点剖析 Observer 类的具体实现逻辑。
本文补充说明前文未涉及的 Observer 部分,首先参考官方文档中的核心流程图:

Observer 的核心职责是完成流程图中“touch -Data(getter) - Collect as Dependency”这一环节,即实现数据的依赖收集。
数据观察与 defineReactive 实现
以下面的代码示例为基础进行梳理:
(注:左右滑动即可查看完整代码,下同)
varvm = newVue({
el: '#demo',
data: {
firstName: 'Hello',
fullName: ''
},
watch: {
firstName(val) {
this.fullName = val + 'TalkingData';
},
}
})
通过还原 Vue 实例化的过程,从初始化到 Observer 类的源码路径如下(省略了非核心讨论的代码):
// src/core/instance/index.js
functionVue(options) {
if(process.env.NODE_ENV !== 'production'&&
!(thisinstanceofVue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// src/core/instance/init.js
Vue.prototype._init = function(options?: Object) {
constvm: Component = this
// ...
initState(vm)
// ...
}
// src/core/instance/state.js
exportfunctioninitState(vm: Component) {
// ...
constopts = vm.$options
if(opts.data) {
initData(vm)
}
// ...
}
functioninitData(vm: Component) {
letdata = vm.$options.data
data = vm._data = typeofdata === 'function'
? getData(data, vm)
: data || {}
// ...
// observe data
observe(data, true/* asRootData */)
}
在 initData 方法中,开始对 data 选项中的数据进行“观察”,将所有数据转换为 observable 状态。接下来查看 observe 方法的具体代码:
// src/core/observer/index.js
functionobserve(value: any, asRootData: ?boolean): Observer| void{
// 如果不是对象,直接返回
if(!isObject(value) || value instanceofVNode) {
return
}
letob: Observer | void
if(hasOwn(value, '__ob__') && value.__ob__ instanceofObserver) {
// 如果有实例则返回实例
ob = value.__ob__
} elseif(
// 确保value是单纯的对象,而不是函数或者是Regexp等情况
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 实例化一个 Observer
ob = newObserver(value)
}
if(asRootData && ob) {
ob.vmCount++
}
returnob
}
observe 方法负责为 data 创建 Observer 实例并返回。如果数据已存在 ob 属性,说明已有 Observer 实例,则直接返回现有实例。Vue 响应式数据均带有 ob 属性,用于存储对应的 Observer 实例,从而避免重复绑定。接着分析 new Observer(value) 的执行过程:
exportclassObserver{
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor(value: any) {
this.value = value
this.dep = newDep()
this.vmCount = 0
def(value, '__ob__', this)
if(Array.isArray(value)) {
// ...
this.observeArray(value)
} else{
this.walk(value)
}
}
walk (obj: Object) {
constkeys = Object.keys(obj)
for(leti = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
observeArray (items: Array<any>) {
for(leti = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
从源码可见,Observer 实例化主要执行两项判断:若值为数组,则遍历数组每一项并再次调用 observe 方法进行观察;若为非数组对象,则遍历其所有属性并调用 defineReactive 方法。defineReactive 是核心所在,它利用 Object.defineProperty 为每个需观察的属性添加 getter 和 setter,从而完成依赖收集。收集完成后,每个属性关联一个 Dep 实例以保存所有相关的 Watcher 对象。以文章开头的例子为例,firstName 和 fullName 分别被添加了 getter/setter,且各自拥有独立的 Dep 实例来存储观察它们的 Watcher。以下是 defineReactive 的源码:
exportfunctiondefineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
constdep = newDep()
// 获取属性的自身描述符
constproperty = Object.getOwnPropertyDeor(obj, key)
if(property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 检查属性之前是否设置了 getter/setter
// 如果设置了,则在之后的 get/set 方法中执行设置了的 getter/setter
constgetter = property && property.get
constsetter = property && property.set
// 通过对属性再次调用 observe 方法来判断是否有子对象
// 如果有子对象,对子对象也进行依赖搜集
letchildOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: functionreactiveGetter() {
// 如果属性原本拥有getter方法则执行
constvalue = getter ? getter.call(obj) : val
if(Dep.target) {
// 进行依赖收集
dep.depend()
if(childOb) {
// 如果有子对象,对子对象也进行依赖搜集
childOb.dep.depend()
// 如果属性是数组,则对每一个项都进行依赖收集
// 如果某一项还是数组,则递归
if(Array.isArray(value)) {
dependArray(value)
}
}
}
returnvalue
},
set: functionreactiveSetter(newVal) {
// 如果属性原本拥有getter方法则执行
// 通过getter方法获取当前值,与新值进行比较
// 如果新旧值一样则不需要执行下面的操作
constvalue = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if(newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if(process.env.NODE_ENV !== 'production'&& customSetter) {
customSetter()
}
if(setter) {
// 如果属性原本拥有setter方法则执行
setter.call(obj, newVal)
} else{
// 如果原本没有setter则直接赋新值
val = newVal
}
// 判断新的值是否有子对象,有的话继续观察子对象
childOb = !shallow && observe(newVal)
// 通知所有的观察者,更新状态
dep.notify()
}
})
}
根据源码中的中文注释,可以清晰理解 defineReactive 的执行逻辑。整个过程本质上是递归操作,为每个属性添加 getter/setter。对于 getter/setter,同样需要对子对象进行递归判断以完成观察者模式。其中,getter 用于执行依赖收集,即源码中的 dep.depend();setter 则在数据变更时触发,发布更新消息并通知所有观察者进行状态更新,即源码中的 dep.notify()。
总结
总结
以上介绍了 Vue 源码中 Observer 的实现过程,希望这对大家理解 Vue 响应式原理有所帮助。如有疑问请留言,小编会及时回复。感谢大家对本站的支持!