Vue源码解析_数据响应式原理 发表于 2024年05月02日 更新于 2024年06月08日
VUE vue Vue源码解析_数据响应式原理 OHNII 2024年05月02日 2024年06月08日 什么是数据响应式 数据响应式就是:当数据发生变化时,使用这个数据的地方会自动更新
对比示例 1 2 3 4 5 6 7 8 9 10 let message = 'Hello' message = 'World' const vm = new Vue ({ data : { message : 'Hello' }, template : '<div>{{ message }}</div>' }) vm.message = 'World'
核心思想
数据驱动 :数据变化,视图跟着变
自动追踪 :Vue自动知道哪些地方用了这个数据
批量更新 :多次数据变化合并成一次DOM更新
基本流程
数据劫持 :拦截对数据的访问和修改
依赖收集 :记录哪些地方使用了这个数据
派发更新 :数据变化时通知所有依赖的地方
视图更新 :重新渲染相关的DOM
Vue2.0的响应式原理 Vue2.0使用Object.defineProperty来实现数据响应式,这是ES5的特性,兼容性较好。
Object的变化侦测 基本原理 Vue2.0通过Object.defineProperty为对象的每个属性定义getter和setter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function defineReactive (obj, key, val ) { const dep = new Dep () Object .defineProperty (obj, key, { get ( ) { dep.depend () return val }, set (newVal ) { if (newVal === val) return val = newVal dep.notify () } }) }
依赖收集和派发更新 依赖收集 :当访问数据时,getter被触发,收集当前的Watcher派发更新 :当数据变化时,setter被触发,通知所有依赖的Watcher
Watcher如何将自己添加到依赖管理器?
当创建Watcher实例时,依赖收集的完整过程如下:
实例化Watcher :执行构造函数,调用this.get()方法
设置全局目标 :Dep.target = this,将当前Watcher实例赋给全局唯一对象
读取数据 :this.getter.call(vm, vm),触发数据的getter
收集依赖 :getter中调用dep.depend(),从Dep.target获取Watcher并存入依赖数组
释放目标 :Dep.target = null,清空全局目标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 get ( ) { Dep .target = this const value = this .getter .call (this .vm , this .vm ) Dep .target = null return value } get ( ) { dep.depend () return val } depend ( ) { if (Dep .target ) { Dep .target .addDep (this ) } }
简单总结 :
Watcher先把自己设置到全局唯一位置(Dep.target)
然后读取数据,触发getter
getter从全局位置读取Watcher,收集到Dep中
数据变化时,Dep通知所有Watcher更新
通过这种方式,Watcher可以主动订阅任意数据的变化
深度侦测 递归为对象的所有属性设置响应式:
1 2 3 4 5 6 7 8 function observe (obj ) { if (!obj || typeof obj !== 'object' ) return Object .keys (obj).forEach (key => { defineReactive (obj, key, obj[key]) observe (obj[key]) }) }
局限性
无法检测属性的添加和删除
1 2 3 4 5 6 data.age = 25 delete data.name Vue .set (data, 'age' , 25 )Vue .delete (data, 'name' )
无法检测数组索引变化
1 2 arr[0 ] = 'x' arr.length = 0
性能问题 :需要递归遍历所有属性
Array的变化侦测 由于Object.defineProperty无法检测数组索引变化,Vue2.0重写了数组的变异方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const arrayMethods = ['push' , 'pop' , 'shift' , 'unshift' , 'splice' , 'sort' , 'reverse' ]arrayMethods.forEach (method => { const original = Array .prototype [method] Object .defineProperty (arrayProto, method, { value : function (...args ) { const result = original.apply (this , args) const ob = this .__ob__ ob.dep .notify () return result } }) })
局限性 :
无法检测索引变化:arr[0] = 'x' ✗
无法检测长度变化:arr.length = 0 ✗
解决方案:使用Vue.set(arr, 0, 'x')或arr.splice(0, 1, 'x')
Vue3.0的响应式原理 Vue3.0使用ES6的Proxy来重写响应式系统,解决了Vue2.0的诸多局限性。
Proxy的基本概念 Proxy可以创建一个对象的代理,拦截对该对象的各种操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const target = { name : '张三' , age : 25 }const handler = { get (target, prop ) { console .log (`读取属性:${prop} ` ) return target[prop] }, set (target, prop, value ) { console .log (`设置属性:${prop} ,新值:${value} ` ) target[prop] = value return true } } const proxy = new Proxy (target, handler)console .log (proxy.name ) proxy.age = 26
Vue3.0的响应式实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function reactive (target ) { return new Proxy (target, { get (target, key, receiver ) { track (target, key) const result = Reflect .get (target, key, receiver) if (isObject (result)) { return reactive (result) } return result }, set (target, key, value, receiver ) { const oldValue = target[key] const result = Reflect .set (target, key, value, receiver) if (oldValue !== value) { trigger (target, key) } return result }, deleteProperty (target, key ) { const hadKey = hasOwn (target, key) const result = Reflect .deleteProperty (target, key) if (result && hadKey) { trigger (target, key) } return result } }) }
Proxy的优势
支持动态属性 :data.age = 25 ✓
支持数组索引变化 :arr[0] = 'x' ✓
支持更多数据结构 :Map、Set等
更好的性能 :按需创建响应式
Proxy的局限性
兼容性问题 :不支持IE浏览器
无法检测原始值 :需要使用ref
响应式系统的核心组件 Dep(依赖收集器) 负责管理依赖关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Dep { constructor ( ) { this .subs = [] } depend ( ) { if (Dep .target ) { Dep .target .addDep (this ) } } notify ( ) { this .subs .forEach (sub => sub.update ()) } }
Watcher(观察者) 负责观察数据变化并执行更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Watcher { constructor (vm, expOrFn, cb ) { this .vm = vm this .cb = cb this .getter = parsePath (expOrFn) this .value = this .get () } get ( ) { Dep .target = this const value = this .getter .call (this .vm , this .vm ) Dep .target = null return value } update ( ) { const oldValue = this .value const newValue = this .get () if (oldValue !== newValue) { this .value = newValue this .cb .call (this .vm , newValue, oldValue) } } }
Watcher的工作流程 :
构造函数中调用get()方法
get()方法设置Dep.target = this
读取数据触发getter,getter收集依赖
清空Dep.target
数据变化时,update()方法执行回调更新视图
Observer(观察者) 负责将普通对象转换为响应式对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Observer { constructor (value ) { this .value = value this .dep = new Dep () if (Array .isArray (value)) { this .observeArray (value) } else { this .walk (value) } } walk (obj ) { Object .keys (obj).forEach (key => { defineReactive (obj, key, obj[key]) }) } }
完整的响应式流程 1. 初始化阶段 Vue实例创建时,调用initData初始化数据:
1 2 3 4 5 6 7 function initData (vm ) { let data = vm.$options .data data = vm._data = typeof data === 'function' ? data.call (vm) : data || {} observe (data, true ) }
2. 数据劫持阶段 observe函数为每个对象创建Observer实例:
1 2 3 4 5 6 7 8 9 10 11 12 function observe (value, asRootData ) { if (!isObject (value) || value instanceof VNode ) return let ob if (hasOwn (value, '__ob__' ) && value.__ob__ instanceof Observer ) { ob = value.__ob__ } else { ob = new Observer (value) } return ob }
3. 依赖收集阶段 模板编译时创建Watcher观察数据:
1 2 3 4 5 6 7 function mountComponent (vm, el ) { const updateComponent = ( ) => { vm._update (vm._render (), hydrating) } new Watcher (vm, updateComponent, noop, {}, true ) }
4. 派发更新阶段 数据变化时触发setter,通知所有依赖的Watcher:
1 2 3 4 function setter (newVal ) { val = newVal dep.notify () }
5. 异步更新队列 Vue使用异步更新队列优化性能:
1 2 3 4 5 6 7 8 9 10 11 12 function queueWatcher (watcher ) { const id = watcher.id if (has[id] == null ) { has[id] = true queue.push (watcher) if (!waiting) { waiting = true nextTick (flushSchedulerQueue) } } }
响应式流程图 1 2 3 4 初始化:Vue实例创建 → initData → observe → new Observer → defineReactive 依赖收集:模板编译 → new Watcher → get() → getter触发 → dep.depend() 派发更新:数据变化 → setter触发 → dep.notify() → watcher.update() 异步更新:update() → queueWatcher() → nextTick() → DOM更新
依赖收集的详细流程 :
Watcher创建 :new Watcher() → 构造函数调用get()
设置目标 :Dep.target = this → 将Watcher设为全局目标
读取数据 :this.getter() → 触发数据的getter
收集依赖 :dep.depend() → 从Dep.target获取Watcher并收集
释放目标 :Dep.target = null → 清空全局目标
数据变化 :setter触发 → dep.notify() → 通知所有Watcher更新
实际运行示例 1 2 3 4 5 6 7 8 9 const vm = new Vue ({ data : { message : 'Hello Vue' }, template : '<div>{{ message }}</div>' })
总结 Vue2.0特点
基于Object.defineProperty :兼容性好,但无法检测动态属性
数组方法重写 :支持push、pop等操作
局限性 :无法检测属性添加/删除、数组索引变化
Vue3.0特点
基于Proxy :功能更强大,支持动态属性
支持数组索引变化 :arr[0] = 'x' ✓
更好的性能 :按需创建响应式
核心要点
响应式是Vue的核心 :数据驱动视图更新
核心组件 :Dep(依赖收集器)、Watcher(观察者)、Observer(观察者)
完整流程 :初始化 → 数据劫持 → 依赖收集 → 派发更新 → 异步更新