破防了!原来Vue响应式设计的如此巧妙
创作时间:
作者:
@小白创作中心
破防了!原来Vue响应式设计的如此巧妙
引用
CSDN
1.
https://blog.csdn.net/zz_jesse/article/details/146356605
Vue的响应式系统是其最核心的特性之一,它让我们通过简单地声明数据与视图的关系,而不需要手动操作DOM。本文通过对Vue2的源码的学习和理解来串一下整体设计。
响应式系统的基本原理
Vue2的响应式系统基于JS的Object.defineProperty API实现。他的系统设计目标很简单:当数据变化时,自动更新与之相关的视图。
核心模块
Vue的响应式系统主要由三个核心部分组成:
- Observer(观察者):负责将普通JavaScript对象转换为响应式对象
- Dep(依赖收集器):用于收集和管理依赖
- Watcher(监听器):代表一个依赖,当数据变化时被通知
源码解析
1. Observer - 数据的"间谍"
Observer类位于src/core/observer/index.js,它的主要工作是将普通对象的属性转换为getter/setter:
// 简化版 Observer 类
export class Observer {
constructor(value) {
this.value = value
this.dep = new Dep()
// 给对象添加 __ob__ 属性,指向 Observer 实例
def(value, '__ob__', this)
if (Array.isArray(value)) {
// 数组的特殊处理...
} else {
// 对象的处理:遍历对象的每个属性,转换为 getter/setter
this.walk(value)
}
}
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
2.defineReactive - 响应式转换的核心
defineReactive函数是实现响应式的核心,它使用Object.defineProperty重新定义对象的属性:
// 简化版 defineReactive
export function defineReactive(obj, key, val) {
// 每个属性都有自己的依赖收集器
const dep = new Dep()
// 获取属性的当前值
val = val || obj[key]
// 如果值是对象,递归使其响应式
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// getter: 读取属性时收集依赖
get: function reactiveGetter() {
// 如果有当前正在计算的 watcher,就收集它作为依赖
if (Dep.target) {
dep.depend() // 收集依赖
if (childOb) {
childOb.dep.depend() // 对象本身也作为依赖
}
}
return val
},
// setter: 修改属性时通知依赖更新
set: function reactiveSetter(newVal) {
if (newVal === val) return
val = newVal
// 如果新值是对象,也要使其响应式
childOb = observe(newVal)
// 通知所有依赖进行更新
dep.notify()
}
})
}
这段代码就像给每个属性安装了"感应器":当你读取属性时,Vue会记录谁在使用这个属性;当你修改属性时,Vue会通知所有用到这个属性的地方进行更新。
3. Dep - 依赖收集器
Dep类位于src/core/observer/dep.js,它的职责是收集和管理依赖,以及在数据变化时通知这些依赖:
// 简化版 Dep
export default class Dep {
constructor() {
this.subs = [] // 存储依赖的数组
}
// 添加依赖
addSub(sub) {
this.subs.push(sub)
}
// 移除依赖
removeSub(sub) {
const index = this.subs.indexOf(sub)
if (index > -1) {
this.subs.splice(index, 1)
}
}
// 收集当前 watcher 作为依赖
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 通知所有依赖更新
notify() {
// 复制一份依赖数组,防止更新过程中数组变化
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 通知每个 watcher 更新
}
}
}
// 全局唯一的当前正在计算的 watcher
Dep.target = null
Dep就像一个通讯录,记录了谁对某个数据感兴趣,当数据变化时,它会逐个通知这些"订阅者"。
4. Watcher - 变化的观察者
Watcher类位于src/core/observer/watcher.js,它代表一个依赖,可以是组件的渲染函数、计算属性或用户使用$watch创建的观察者:
// 简化版 Watcher
export default class Watcher {
constructor(vm, expOrFn, cb, options) {
this.vm = vm
// 表达式或函数,用于获取监听的值
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn) // 解析表达式
}
this.cb = cb // 值变化时的回调函数
this.deps = [] // 记录这个 watcher 依赖了哪些 dep
this.depIds = new Set()
// 立即执行一次 getter,收集依赖
this.value = this.get()
}
// 获取值并收集依赖
get() {
// 设置当前正在计算的 watcher
Dep.target = this
let value
try {
// 执行 getter,这会触发属性的 getter,从而收集依赖
value = this.getter.call(this.vm, this.vm)
} finally {
// 清除当前 watcher
Dep.target = null
}
return value
}
// 添加依赖
addDep(dep) {
const id = dep.id
if (!this.depIds.has(id)) {
this.depIds.add(id)
this.deps.push(dep)
dep.addSub(this)
}
}
// 更新
update() {
// 异步更新队列
queueWatcher(this)
}
// 实际执行更新的方法
run() {
const oldValue = this.value
const newValue = this.get()
if (newValue !== oldValue) {
// 调用回调函数
this.cb.call(this.vm, newValue, oldValue)
}
}
}
Watcher就像是一个"侦探",负责监视数据的变化,并在变化时执行特定的操作,如更新视图。
响应式系统的工作流程
现在用一个简单的例子来说下整个流程:
new Vue({
el: '#app',
data: {
message: 'Hello'
}
})
- 初始化阶段:
- Vue实例化时,对data中的message属性调用defineReactive
- 这会为message创建一个Dep实例,并设置getter/setter
- 依赖收集阶段:
- 当渲染模板{{ message }}时,会创建一个渲染Watcher
- 在渲染过程中,会读取message属性,触发其getter
- getter中发现有当前Watcher (Dep.target),于是调用dep.depend()收集依赖
- 这样,message的Dep就收集了渲染Watcher作为依赖
- 更新阶段:
- 当我们执行vm.message = 'Hi'时,触发message的setter
- setter中调用dep.notify()通知依赖更新
- Dep通知所有收集的Watcher执行update方法
- 渲染Watcher重新渲染组件,更新视图
响应式系统的巧妙之处
Vue响应式系统的巧妙之处在于:
- 自动依赖收集:不需要手动声明依赖关系,Vue会在数据被访问时自动建立依赖关系
- 精确更新:只有真正依赖某个数据的视图才会在该数据变化时更新,而不是整个应用重新渲染
- 深度响应:对象的嵌套属性也能被监听,因为Observer会递归地将嵌套对象也变成响应式的
响应式系统的局限性
Vue 2响应式系统也有一些局限:
- 不能检测对象属性的添加或删除:这是因为Object.defineProperty只能劫持已经存在的属性
- 数组变化的部分限制:Vue重写了数组的变异方法(如push、pop等),但不能检测通过索引设置数组项或修改数组长度的变化
这些限制在Vue 3中通过使用ES6的Proxy代替Object.defineProperty得到了解决。
热门推荐
2024河北最好大学排名:两所大学进全国100强,河北工业大学排第3
蛇年女孩名字大全2025最新版
它是地球最耐辐射生物,或成火星移民“先锋”
最详细的长江游轮登船指南:从行李寄存到餐食升级全攻略
桂林提取国管公积金需要什么条件?
捞面条配什么菜好吃?这些你吃过吗?
挖矿费用包括哪些?怎么算?
期货保证金低于多少会强平?详解强平机制及风险控制
雨水收集系统如何做好维护呢?
谷丙转氨酶超过多少算严重?
加拿大购车贷款全攻略:银行、经销商、信用社三大渠道对比
当单纯高热惊厥后的脑电图出现癫痫波时应如何处理
斯大林和一生之敌希特勒
广西河池宜州区:五乡三地一城,千年文化魅力绽放
当游戏成为孩子的避风港 家长如何正确应对
深圳发力宠物经济:产业园落地,政策频出,打造“人宠友好型”城市
从敦煌壁画中浅析唐代服饰风尚及形成的原因
周末低成本带孩子见世面的100种方式
从有到优,家医签约步履不停
兴森科技涨停:华为海思与DRAM市场再掀热潮
如何提高孩子的记忆力?小技巧+吃什么
翻译专业都学哪些课程
厦门,何时是你的最佳旅游季?
弹性势能和机械能的转化
如何快速查看显示器型号的多种方法与技巧
巴萨历史瑰宝!梅西签约餐巾纸拍卖,底价惊人,令人瞩目。
孕期可以吃夏威夷果吗
三个感人的故事,狗狗是人类好朋友!
保姆级教程:用Stable Diffusion制作爆款治愈系视频
「排位黑科技」AP韦鲁斯重出江湖连战连捷!韩服王者局黑科技