【Vue原理解析】之响应式系统

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Vue2的响应式系统是核心之一,它使得Vue.js能够实现数据驱动的视图变化。其实现主要基于Object.defineProperty API,通过在数据对象上添加属性监听来实现数据变化时对视图进行更新。vue3实现主要基于Proxy API和Reactive,Reactive函数负责将一个普通的JavaScript对象转换成响应式对象。它通过递归遍历对象的所有属性,并使用Proxy代理对象来实现对属性的拦截。

引言

Vue2的响应式系统是核心之一,它使得Vue.js能够实现数据驱动的视图变化。其实现主要基于Object.defineProperty API,通过在数据对象上添加属性监听来实现数据变化时对视图进行更新。

vue3实现主要基于Proxy API和Reactive,Reactive函数负责将一个普通的JavaScript对象转换成响应式对象。它通过递归遍历对象的所有属性,并使用Proxy代理对象来实现对属性的拦截。

Vue2.x响应式系统

在Vue.js中,响应式系统主要分为两部分:数据劫持和发布订阅。

数据劫持:通过使用Object.defineProperty API来对数据对象的属性进行劫持,在属性get和set时添加钩子函数,在get时记录依赖,在set时通知观察者更新视图。

发布订阅:Vue.js通过实现一个自己的发布订阅模型来实现响应式系统,通过依赖收集器来收集所有依赖,并在依赖变化时触发通知器进行视图更新。

具体来说,Vue2.x的响应式原理主要是通过Observer、Dep和Watcher三个核心组件来实现的。

Vue2.x源码解析

下面是Vue2.x响应式原理源码解析:

1. Observer

用于收集数据属性的依赖,并在数据发生变化时通知订阅者进行更新。

Observer负责将一个普通的JavaScript对象转换成响应式对象。它通过递归遍历对象的所有属性,并使用Object.defineProperty方法为每个属性设置getter和setter。在getter中,Observer会收集当前正在执行的Watcher作为依赖。在setter中,Observer会触发依赖更新,并通知相关的Watcher进行更新。

classObserver {
constructor(value) {
// this.value = valuethis.dep=newDep()
this.vmCount=0def(value, '__ob__', this)
if (isArray(value)) {
// 数组处理// ...    } else {
// 对象处理constkeys=Object.keys(value)
for (leti=0; i<keys.length; i++) {
constkey=keys[i]
defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
      }
    }
  }
}
  1. 在Observer构造函数中,创建一个Dep实例作为依赖收集器。然后,通过def函数将Observer实例添加到value对象的__ob__属性上,这样可以在后续操作中方便地获取到Observer实例。
  2. 接下来,根据value的类型进行不同的处理。如果是数组,则调用数组处理逻辑;如果是对象,则调用对象处理逻辑。
  3. 在对象处理逻辑中,通过Object.keys方法获取对象的所有属性,并遍历每个属性,调用defineReactive函数为每个属性设置getter和setter。

2. Dep(依赖收集器)

用于存储一个或多个依赖关系,在数据发生变化时通知订阅者进行更新。

Dep是一个用于收集依赖和触发更新的类。每个响应式对象都会有一个对应的Dep实例,用于管理该对象所有属性的依赖关系。在getter中,Watcher会将自身添加到Dep实例中,表示该Watcher依赖于该属性。在setter中,Dep实例会通知所有依赖于该属性的Watcher进行更新。

classDep {
...constructor() {
this.subs= []
  }
addSub(sub) {
this.subs.push(sub)
  }
removeSub(sub) {
remove(this.subs, sub)
  }
depend() {
if (Dep.target) {
Dep.target.addDep(this)
    }
  }
notify() {
constsubs=this.subs.slice()
for (leti=0, l=subs.length; i<l; i++) {
constsub=subs[i]
// ...subs.update()
    }
  }
}

在Dep类中,subs数组用于存储所有依赖(即Watcher)。

  • addSub方法用于将一个依赖添加到subs数组中。
  • removeSub方法用于从subs数组中移除一个依赖。
  • depend方法用于将当前正在执行的Watcher添加到Dep实例中。
  • notify方法用于触发所有依赖(即Watcher)进行更新。

3. Watcher(观察者)

用于订阅一个或多个依赖关系,在依赖发生变化时执行相应的回调函数。

Watcher是一个用于订阅和接收属性变化通知的类。它负责创建一个订阅者,并将自身添加到当前正在执行的Dep实例中。当属性发生变化时,Dep实例会通知所有订阅者进行更新。

classWatcher {
constructor(vm, expOrFn, cb) {
if ((this.vm=vm) &&isRenderWatcher) {
vm._watcher=this    }
if (isFunction(expOrFn)) {
this.getter=expOrFn    } else {
this.getter=parsePath(expOrFn)
if (!this.getter) {
this.getter=noop      }
    }
this.value=this.lazy?undefined : this.get()
  }
get() {
pushTarget(this)
letvalueconstvm=this.vmtry {
value=this.getter.call(vm, vm)
    } catch (e) {
// ...    } finally {
if (this.deep) {
traverse(value)
      }
popTarget()
this.cleanupDeps()
    }
returnvalue  }
addDep(dep) {
constid=dep.idif (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
      }
    }
  }
update() {
if (this.lazy) {
this.dirty=true    } elseif (this.sync) {
this.run()
    } else {
queueWatcher(this)
    }
  }
run() {
if (this.active) {
constvalue=this.get()
if (
value!==this.value||isObject(value) ||this.deep      ) {
// ...this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}

在Watcher构造函数中,首先将传入的vm、expOrFn和cb保存到实例的对应属性上。expOrFn可以是一个函数或一个字符串,如果是字符串,则会通过parsePath方法将其解析为一个函数。

get方法用于获取属性的值。在get方法中,会将当前Watcher添加到全局的targetStack中,并将Dep.target设置为当前Watcher。然后通过调用getter方法获取属性的值,并在过程中收集依赖。最后,将Dep.target恢复为上一个Watcher,并返回属性的值。

addDep方法用于将依赖(即Dep实例)添加到当前Watcher中。在addDep方法中,会判断该依赖是否已经被添加过,如果没有,则将其添加到newDeps数组和newDepIds集合中,并判断是否已经被订阅过,如果没有,则调用dep.addSub(this)将当前Watcher添加到依赖(即Dep实例)中。

update方法用于触发更新操作。在update方法中,会调用run方法进行更新。

run方法用于执行更新操作。首先获取最新的属性值,并与旧值进行比较。如果不相等或新值是对象或this.deep为true,则调用回调函数cb进行更新操作。

Vue3源码解析

在Vue3的源码中,createReactiveObject函数是reactive.ts文件中的核心部分,负责创建响应式对象。路径packages/reactivity/src/reactive.ts

createReactiveObject

functioncreateReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>): any {
// 检查目标对象是否为非对象类型,如果是则直接返回if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
    }
returntarget  }
// 如果目标对象已经是一个Proxy,则直接返回它// 例外情况:在一个响应式对象上调用readonly()函数if (
target[ReactiveFlags.RAW] &&!(isReadonly&&target[ReactiveFlags.IS_REACTIVE])
  ) {
returntarget  }
// 检查目标对象是否已经存在对应的代理对象,如果存在则直接返回缓存的代理对象constexistingProxy=proxyMap.get(target)
if (existingProxy) {
returnexistingProxy  }
// 只有特定类型的值可以被观察consttargetType=getTargetType(target)
if (targetType===TargetType.INVALID) {
returntarget  }
// 创建一个新的代理对象proxyconstproxy=newProxy(
target,
targetType===TargetType.COLLECTION?collectionHandlers : baseHandlers  )
// 将代理对象proxy缓存到proxyMap中proxyMap.set(target, proxy)
returnproxy}

在这个函数中,首先会检查目标对象是否为非对象类型,如果是则直接返回。然后会检查目标对象是否已经存在对应的代理对象,如果存在则直接返回缓存的代理对象。

接下来,会根据传入的参数选择相应的处理器(baseHandlers或collectionHandlers),并使用new Proxy创建一个代理对象proxy。

最后,将代理对象proxy缓存到proxyMap中,并返回该代理对象。

通过这个函数,Vue3实现了对目标对象的响应式转换,并缓存了代理对象以避免重复创建。

effect

文件路径packages/reactivity/src/effect.ts

在Vue3的源码中,effect.ts文件包含了effecttriggertrack这些核心源码,它们在Vue3的响应式系统中扮演着重要的角色。下面是对这些核心源码及其作用的大致讲解:

exportfunctioneffect<T=any>(
fn: () =>T,
options?: ReactiveEffectOptions): ReactiveEffectRunner {
if ((fnasReactiveEffectRunner).effectinstanceofReactiveEffect) {
fn= (fnasReactiveEffectRunner).effect.fn  }
const_effect=newReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
  }
if (!options||!options.lazy) {
_effect.run()
  }
construnner=_effect.run.bind(_effect) asReactiveEffectRunnerrunner.effect=_effectreturnrunner}
  • effect函数用于创建一个副作用函数,该副作用函数会自动追踪其依赖,并在依赖变化时自动重新执行。
  • effect(fn, options)接受一个函数 fn 和一个可选的选项对象 options
  • 在内部,它通过调用 createReactiveEffect(fn, options) 创建了一个 ReactiveEffect 实例 _effect
  • 如果选项中没有设置 lazy: true(即立即执行),则会调用 _effect.run() 来执行副作用函数。
  • 最后,创建并返回一个运行器 runner = _effect.run.bind(_effect),并将 _effect 实例赋值给运行器的属性 runner.effect = _effect

track

  • track(target, key)函数用于收集当前正在执行的副作用函数的依赖。
  • 它通过调用 track(target, key) 来进行依赖收集。
  • 在内部,它使用了一个名为 targetMap 的 WeakMap 来存储依赖关系。它以目标对象为键,以属性的依赖集合为值。
  • 当访问响应式对象的属性时,会获取当前正在执行的副作用函数,并将其添加到对应属性的依赖集合中。

trigger

  • trigger函数用于触发依赖更新,即执行所有依赖该属性的副作用函数。
  • 在内部,它使用了一个名为 targetMap 的 WeakMap 来获取存储在追踪阶段收集到的依赖关系。
  • 它遍历所有相关联的副作用函数,并执行它们。

通过这些核心源码,Vue3实现了响应式系统中的副作用追踪和依赖更新。effect函数用于创建副作用函数,track函数用于收集依赖,trigger函数用于触发更新。它们共同协作,实现了Vue3的响应式原理。

总结

Vue2和Vue3在响应式系统的实现上有一些重要的区别,下面是它们之间的主要区别:

  1. 实现方式:
  • Vue2使用Object.defineProperty来实现响应式。它通过在对象上定义getter和setter来拦截对属性的访问和修改,从而实现依赖收集和触发更新。
  • Vue3使用Proxy来实现响应式。Proxy是ES6中新增的特性,它可以拦截对象上的各种操作,包括属性访问、修改、删除等。Vue3利用Proxy的强大拦截能力来追踪依赖并触发更新。
  1. 性能优化:
  • Vue2在每个组件实例化时都会为数据对象进行递归遍历,并为每个属性设置getter和setter。这样会导致初始化时的性能开销较大。
  • Vue3通过Proxy实现了惰性创建副作用函数(effect),只有当副作用函数被真正使用时才会进行依赖收集。这样可以减少不必要的依赖收集和更新操作,提高了性能。
  1. 依赖追踪:
  • Vue2使用全局变量Dep来追踪依赖关系,并将Watcher与Dep进行关联。每个属性都有一个对应的Dep实例,当属性被访问时,Watcher会将自身添加到Dep中,当属性发生变化时,Dep会通知所有关联的Watcher进行更新。
  • Vue3使用WeakMap来存储依赖关系,将对象作为键,将属性的依赖集合作为值。这样可以避免内存泄漏,并且不需要全局变量来追踪依赖。
  1. 嵌套属性和数组:
  • Vue2对于嵌套属性和数组的处理较为复杂。对于嵌套属性,需要递归调用Observer进行响应式转换;对于数组,需要重写数组的一些方法来拦截变更操作。
  • Vue3通过Proxy的拦截能力可以直接处理嵌套属性和数组。无需递归调用Observer或重写数组方法。
  1. TypeScript支持:
  • Vue3对TypeScript提供了更好的支持,并且在源码中使用了大量的TypeScript类型定义,提高了开发效率和代码可靠性。

总体而言,Vue3在响应式系统上进行了一系列改进和优化,提升了性能、可维护性和开发体验。同时引入Composition API以及对TypeScript的支持也使得开发更加灵活和可靠。

目录
相关文章
|
10天前
|
安全 前端开发 Android开发
探索移动应用与系统:从开发到操作系统的深度解析
在数字化时代的浪潮中,移动应用和操作系统成为了我们日常生活的重要组成部分。本文将深入探讨移动应用的开发流程、关键技术和最佳实践,同时分析移动操作系统的核心功能、架构和安全性。通过实际案例和代码示例,我们将揭示如何构建高效、安全且用户友好的移动应用,并理解不同操作系统之间的差异及其对应用开发的影响。无论你是开发者还是对移动技术感兴趣的读者,这篇文章都将为你提供宝贵的见解和知识。
|
15天前
|
负载均衡 网络协议 算法
Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式
本文探讨了Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式,以及软件负载均衡器、云服务负载均衡、容器编排工具等实现手段,强调两者结合的重要性及面临挑战的应对措施。
40 3
|
18天前
|
机器学习/深度学习 人工智能 数据处理
【AI系统】NV Switch 深度解析
英伟达的NVSwitch技术是高性能计算领域的重大突破,旨在解决多GPU系统中数据传输的瓶颈问题。通过提供比PCIe高10倍的带宽,NVLink实现了GPU间的直接数据交换,减少了延迟,提高了吞吐量。NVSwitch则进一步推动了这一技术的发展,支持更多NVLink接口,实现无阻塞的全互联GPU系统,极大提升了数据交换效率和系统灵活性,为构建强大的计算集群奠定了基础。
43 3
|
21天前
|
JavaScript 前端开发 开发者
Vue是如何劫持响应式对象的
Vue是如何劫持响应式对象的
20 1
|
21天前
|
JavaScript 前端开发 API
介绍一下Vue中的响应式原理
介绍一下Vue中的响应式原理
27 1
|
24天前
|
监控 JavaScript 算法
深度剖析 Vue.js 响应式原理:从数据劫持到视图更新的全流程详解
本文深入解析Vue.js的响应式机制,从数据劫持到视图更新的全过程,详细讲解了其实现原理和运作流程。
|
26天前
|
JavaScript
Vue 双向数据绑定原理
Vue的双向数据绑定通过其核心的响应式系统实现,主要由Observer、Compiler和Watcher三个部分组成。Observer负责观察数据对象的所有属性,将其转换为getter和setter;Compiler解析模板指令,初始化视图并订阅数据变化;Watcher作为连接Observer和Compiler的桥梁,当数据变化时触发相应的更新操作。这种机制确保了数据模型与视图之间的自动同步。
|
28天前
|
网络协议 网络安全 网络虚拟化
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算。通过这些术语的详细解释,帮助读者更好地理解和应用网络技术,应对数字化时代的挑战和机遇。
69 3
|
1月前
|
缓存 JavaScript 搜索推荐
Vue SSR(服务端渲染)预渲染的工作原理
【10月更文挑战第23天】Vue SSR 预渲染通过一系列复杂的步骤和机制,实现了在服务器端生成静态 HTML 页面的目标。它为提升 Vue 应用的性能、SEO 效果以及用户体验提供了有力的支持。随着技术的不断发展,Vue SSR 预渲染技术也将不断完善和创新,以适应不断变化的互联网环境和用户需求。
65 9
|
17天前
|
前端开发 Android开发 UED
移动应用与系统:从开发到优化的全面解析####
本文深入探讨了移动应用开发的全过程,从最初的构思到最终的发布,并详细阐述了移动操作系统对应用性能和用户体验的影响。通过分析当前主流移动操作系统的特性及差异,本文旨在为开发者提供一套全面的开发与优化指南,确保应用在不同平台上均能实现最佳表现。 ####
21 0

推荐镜像

更多