当前位置: 首页 > news >正文

二、什么是Vue中的响应式?Vue的响应式原理

什么是Vue中的响应式

Vue中的响应式,简而言之就是当数据发生变化时,页面跟随变化。使用过Vue的v-model都有比较深刻的感受,我们在代码中修改双向绑定的数据后,页面上的数据也会自动更新,页面跟随变化
我们看个例子:

<div id="app"><div>价格:¥{{ price }}</div><div>支付金额:¥{{ price * quantity }}</div><button @click="changePrice">改变价格</button>
</div>
var app = new Vue({el: '#app',data() {return {price: 5.0,quantity: 2};},methods: {changePrice() {this.price = 10;}}
})

在这个例子中,我们调用changePrice方法,修改price的值,页面上价格 、支付金额,都会自动改变。这就是Vue中的响应式

Vue中如何实现响应式

当数据发生变化后,会重新对页面渲染,这就是Vue响应式,那么这一切是怎么做到的呢?

想完成这个过程,我们需要完成这几个步骤:

(1)侦测数据的变化(数据劫持 / 数据代理)
(2)收集视图依赖了哪些数据(依赖收集)
(3)数据变化时,自动“通知”需要更新的视图部分,并进行更新(发布订阅模式)

1.侦测数据的变化(数据劫持 / 数据代理)

在Javascript中,如何侦测一个对象的变化?我们有两种办法可以侦测到变化:使用Object.defineProperty和ES6的Proxy,这就是进行数据劫持或数据代理。

在Vue2中,使用的是Object.defineProperty的方式侦测数据的变化,
类似如下代码,当属性被读取或设置时,相应的getter或setter将被调用。修改obj.value的值时,会监听到obj.value,执行set方法,执行console.log(obj.value)时,又会调用get方法。

    <script>const obj = {};// 定义属性'value',包含getter和setterObject.defineProperty(obj, "value", {get() {console.log("get value");return this._value;},set(newValue) {console.log("set value");this._value = newValue;},// 可以通过这个属性来控制属性的可枚举性configurable: true,// 可以通过这个属性来控制属性的可写性enumerable: true,});obj.value = 5;console.log(obj.value); // get value ,set value, 5</script>

但在Vue3中,使用的是ES6的Proxy的方式侦测数据的变化。
Proxy对象允许你拦截并自定义对象的基本操作,包括属性访问和修改。这使得你可以创建一个代理对象,当访问或修改目标对象的属性时,会触发预定义的行为。如下代码示例:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>基础 ref()</title></head><body><button id="updateButton">点击+1</button><div id="message"></div><script>const createRef = (initialValue) => {return new Proxy({ value: initialValue },{get(target, key) {return target[key];},set(target, key, value) {target[key] = value;if (key === "value") {updateDisplay(); // 当.value被设置时,更新DOM}return true;},});};// 初始化refconst numberRef = createRef(0);// 更新DOM的函数const updateDisplay = () => {document.getElementById("message").innerText = numberRef.value;};// 绑定按钮点击事件document.getElementById("updateButton").addEventListener("click", () => {numberRef.value++; // 点击按钮时计数器加一});// 初始显示updateDisplay();</script></body>
</html>

2.收集视图依赖了哪些数据(依赖收集)

当模板渲染或计算属性计算时,Vue 会追踪哪些数据被访问了。这通过 Dep 类和 Watcher 类完成。Watcher 会在读取数据时将自身添加到数据的依赖列表中。

订阅者 Dep
收集依赖需要为依赖找一个存储依赖的地方,为此我们创建了Dep,它用来收集依赖、删除依赖和向依赖发送消息等。
于是我们先来实现一个订阅者 Dep 类,用于解耦属性的依赖收集和派发更新操作,说得具体点,它的主要作用是用来存放 Watcher 观察者对象。我们可以把Watcher理解成一个中介的角色,数据发生变化时通知它,然后它再通知其他地方。
Dep的简单实现

class Dep {constructor () {/* 用来存放Watcher对象的数组 */this.subs = [];}/* 在subs中添加一个Watcher对象 */addSub (sub) {this.subs.push(sub);}/* 通知所有Watcher对象更新视图 */notify () {this.subs.forEach((sub) => {sub.update();})}
}

以上代码主要做两件事情:

用 addSub 方法可以在目前的 Dep 对象中增加一个 Watcher 的订阅操作;
用 notify 方法通知目前 Dep 对象的 subs 中的所有 Watcher 对象触发更新操作

观察者 Watcher
Vue 中定义一个 Watcher 类来表示观察订阅依赖。
watcher的简单实现:

class Watcher {constructor(obj, key, cb) {// 将 Dep.target 指向自己// 然后触发属性的 getter 添加监听// 最后将 Dep.target 置空Dep.target = thisthis.cb = cbthis.obj = objthis.key = keythis.value = obj[key]Dep.target = null}update() {// 获得新值this.value = this.obj[this.key]// 我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图this.cb(this.value)}
}

3.数据变化时,自动“通知”需要更新的视图部分,并进行更新(发布订阅模式)

当数据被修改时,对应的 Watcher 会收到通知,并触发视图更新

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Linux驱动学习之点灯(四,linux2.6)
  • 电子电气架构 --- 软件定义汽车需要怎么样的EE架构
  • Excel“取消工作表保护”忘记密码并恢复原始密码
  • 【数学建模】优化模型——两辆平板车装货问题
  • linux应用编程--网络编程(socket编程基础)
  • 企业为什么需要安装加密软件
  • PostgreSQL的postgres主进程
  • 海外媒体投稿:怎样在法国媒体发稿宣传中获得成功
  • linux debian12 离线安装jdk1.8 (解决root和普通用户不能同时拥有java)
  • 【从Qwen2,Apple Intelligence Foundation,Gemma 2,Llama 3.1看大模型的性能提升之路】
  • Java中hashcode()和equals()关系
  • Python爬虫——简单网页抓取(实战案例)小白篇
  • 《Python requests 库详解》
  • [python][代码]Python删除文件方法
  • 嵌入式软件--PCB DAY 2
  • php的引用
  • 11111111
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • CSS 三角实现
  • Docker下部署自己的LNMP工作环境
  • EventListener原理
  • JavaScript DOM 10 - 滚动
  • JavaScript实现分页效果
  • MySQL的数据类型
  • MySQL几个简单SQL的优化
  • SSH 免密登录
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 京东美团研发面经
  • 每天10道Java面试题,跟我走,offer有!
  • 入口文件开始,分析Vue源码实现
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • ​虚拟化系列介绍(十)
  • #{}和${}的区别?
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (强烈推荐)移动端音视频从零到上手(上)
  • (转)拼包函数及网络封包的异常处理(含代码)
  • *上位机的定义
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • .py文件应该怎样打开?
  • ??myeclipse+tomcat
  • @synthesize和@dynamic分别有什么作用?
  • @软考考生,这份软考高分攻略你须知道
  • [ 云计算 | AWS 实践 ] 基于 Amazon S3 协议搭建个人云存储服务
  • [2019.2.28]BZOJ4033 [HAOI2015]树上染色
  • [2019.3.5]BZOJ1934 [Shoi2007]Vote 善意的投票
  • [bzoj1912]异象石(set)
  • [BZOJ5125]小Q的书架(决策单调性+分治DP+树状数组)
  • [CF407E]k-d-sequence
  • [EFI]MSI GF63 Thin 9SCXR电脑 Hackintosh 黑苹果efi引导文件
  • [Flutter]WindowsPlatform上运行遇到的问题总结
  • [JS]认识feach