Vue双向绑定原理

响应式原理

响应式是Vue的核心特性之一,数据驱动视图,我们修改数据视图随之响应更新,其原理是:

⚡️ 通过数据劫持结合发布和-订阅者模式的方式,通过拦截对数据的操作,在数据变动时发 布消息给订阅者,触发相应的监听回调。


发布者-订阅者模式

简单地说,发布者-订阅者模式的流程就是:监听器监听数据状态变化, 一旦数据发生变化,则会通知对应的订阅者,让订阅者执行对应的业务逻辑 。

首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图

  1. 实现一个监听器 Observer ,用来劫持并监听所有属性,如果属性发生变化,就通知订阅者;
  2. 实现一个订阅器 Dep,用来收集订阅者,对监听器 Observer 和 订阅者 Watcher 进行统一管理;
  3. 实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的方法,从而更新视图;
  4. 实现一个解析器 Compile,可以解析每个节点的相关指令,对模板数据和订阅器进行初始化。

在这里插入图片描述

1. 监听器 Observer

监听器 Observer 的实现,主要是指让数据对象变得“可观测”,即每次数据读或写时,我们能感知到数据被读取了或数据被改写了。要使数据变得“可观测”。

  • Vue2.x是借助Object.defineProperty()实现的

    通过Object.defineProperty对data上的数据递归地进行gettersetter操作。也就是对属性的读取、修改进行拦截(数据劫持)

    // 循环遍历数据对象的每个属性
    function observable(obj) {if (!obj || typeof obj !== 'object') {return;}let keys = Object.keys(obj);keys.forEach((key) => {defineReactive(obj, key, obj[key])})return obj;
    }// 将对象的属性用 Object.defineProperty() 进行设置
    function defineReactive(obj, key, val) {enumerable: true,configurable: true,Object.defineProperty(obj, key, {//拦截get,当我们访问data.key时会被这个方法拦截到get() {//在这里收集依赖console.log(`${key}属性被读取了...`);return val;},//拦截set,当我们为data.key赋值时会被这个方法拦截到set(newVal) {//当数据变更时,通知依赖项变更UIconsole.log(`${key}属性被修改了...`);val = newVal;}})
    }
    

    我们通过Object.defineProperty为对象obj添加属性,可以设置对象属性的getter和setter函数。之后我们每次通过点语法获取属性都会执行这里的getter函数,在这个函数中我们会把调用此属性的依赖收集到一个集合中 ;而在我们给属性赋值(修改属性)时,会触发这里定义的setter函数,在次函数中会去通知集合中的依赖更新,做到数据变更驱动视图变更。

  • Vue3.x是借助Proxy实现的
    通过Proxy对象创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
    Proxy的监听是深层次的,监听整个对象,而不是某个属性。Proxy相比Object.defineProperty在处理数组和新增属性的响应式处理上更加方便。

    let nObj=new Proxy(obj,{//拦截get,当我们访问nObj.key时会被这个方法拦截到get: function (target, propKey, receiver) {console.log(`getting ${propKey}!`);return Reflect.get(target, propKey, receiver);},//拦截set,当我们为nObj.key赋值时会被这个方法拦截到set: function (target, propKey, value, receiver) {console.log(`setting ${propKey}!`);return Reflect.set(target, propKey, value, receiver);}
    })
    

.

2. 订阅器 Dep

发布 —订阅设计模式

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态改变时,所有依赖于它的对象都将得到通知。

举例说明:
小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼 MM 告诉小明,不久后还有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买。
但到底是什么时候,目前还没有人能够知道。于是小明把电话号码留在了售楼处。售楼 MM 答应他,只要新楼盘一推出就马上发信息通知小明。
除了小明,还有小红、小强、小龙也是一样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼 MM会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们。
这就是发布-订阅模式在现实中的例子。

发布—订阅模式的优点:

  • 发布-订阅模式广泛应用于异步编程中,这是一种替代传递回调函数的方案,比如,我们可以订阅 ajax 请求的 error 、succ 等事件。在异步编程中使用发布-订阅模式, 我们就无需过多关注对象在异步运行期间的内部状态,而只需要订阅感兴趣的事件发生点。

  • 发布-订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。发布-订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要任何修改;同样发布者需要改变时,也不会影响到之前的订阅者。只要之前约定的事件名没有变化,就 可以自由地改变它们。

订阅器 Dep 实现:

完成了数据的’可观测’,即我们知道了数据在什么时候被读或写了,那么我们就可以在数据被读或写的时候通知那些依赖该数据的视图更新了。为了方便,我们需要先将所有依赖收集起来,一旦数据发生变化,就统一通知更新。
其实,这就是上面所说的“发布订阅者”模式:数据变化为“发布者”,依赖对象为“订阅者”(数据变化时,所有依赖的对象都会得到通知)。

现在,我们需要创建一个依赖收集容器,也就是消息订阅器 Dep,用来容纳所有的“订阅者”。
消息订阅器 Dep 主要负责收集订阅者,然后当数据变化的时候后执行对应订阅者的更新函数。

创建消息订阅器 Dep:

function Dep () {this.subs = [];
}
Dep.prototype = {addSub: function(sub) {this.subs.push(sub);},notify: function() {this.subs.forEach(function(sub) {sub.update();});}
};
Dep.target = null;

有了订阅器,我们再将上面的 defineReactive 函数进行改造一下,向其植入订阅器:

defineReactive: function(data, key, val) {var dep = new Dep();Object.defineProperty(data, key, {enumerable: true,configurable: true,get: function getter () {if (Dep.target) {dep.addSub(Dep.target);}return val;},set: function setter (newVal) {if (newVal === val) {return;}val = newVal;dep.notify();}});
}

上面代码中有一个静态属性 Dep.target,这是一个全局唯一 的Watcher,因为在同一时间只能有一个全局的 Watcher 被计算,另外它的自身属性 subs 也是 Watcher 的数组。

.

3. 订阅者 Watcher

订阅者Watcher 在初始化的时候需要将自己添加进订阅器 Dep 中,那该如何添加呢?我们已经知道监听器Observer 是在 get 函数执行了添加订阅者Wather 的操作的,所以我们只要在订阅者 Watcher 初始化的时候触发对应的 get 函数去执行添加订阅者操作即可,那要如何触发 get 的函数,只要获取对应的属性值就可以触发了,核心原因就是因为我们使用了 Object.defineProperty( ) 进行数据监听。这里还有一个细节点需要处理,我们只要在订阅者 Watcher 初始化的时候才需要添加订阅者,所以需要做一个判断操作,因此可以在订阅器上做一下手脚:在 Dep.target 上缓存下订阅者,添加成功后再将其去掉就可以了。订阅者 Watcher 的实现如下:

function Watcher(vm, exp, cb) {this.vm = vm; // 一个 Vue 的实例对象this.exp = exp; // 是 node 节点的 v-model 等指令的属性值 或者插值符号中的属性。如 v-model="name",exp 就是namethis.cb = cb;// 是 Watcher 绑定的更新函数this.value = this.get();  // 将自己添加到订阅器的操作
}Watcher.prototype = {update: function() {this.run();},run: function() {var value = this.vm.data[this.exp];var oldVal = this.value;if (value !== oldVal) {this.value = value;this.cb.call(this.vm, value, oldVal);}},get: function() {Dep.target = this; // 将自己赋值为全局的订阅者  全局变量 订阅者 赋值  var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数Dep.target = null; // 全局变量 订阅者 释放return value;}
};

4. 解析器 Compile

解析器 Compile 关键逻辑代码分析

通过监听器 Observer 订阅器 Dep 和订阅者 Watcher 的实现,其实就已经实现了一个双向数据绑定的例子,但是整个过程都没有去解析 dom 节点,而是直接固定某个节点进行替换数据的,所以接下来需要实现一个解析器 Compile 来做解析和绑定工作。解析器 Compile 实现步骤:

  • 解析模板指令,并替换模板数据,初始化视图;
  • 将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器;

我们下面对 ‘{{变量}}’ 这种形式的指令处理的关键代码进行分析,感受解析器 Compile 的处理逻辑,关键代码如下:

compileText: function(node, exp) {var self = this;var initText = this.vm[exp]; // 获取属性值this.updateText(node, initText); // dom 更新节点文本值// 将这个指令初始化为一个订阅者,后续 exp 改变时,就会触发这个更新回调,从而更新视图new Watcher(this.vm, exp, function (value) { self.updateText(node, value);});
}

双向数据绑定

双向数据绑定通常是指我们使用的v-model指令的实现,是Vue的一个特性,也可以说是一个input事件value的语法糖。 Vue通过v-model指令为组件添加上input事件处理和value属性的赋值。

<template><input v-model='localValue'/>
</template>

上述的组件就相当于如下代码

<template><!-- 这里添加了input时间的监听和value的属性绑定 --><input @input='onInput' :value='localValue' /><span>{{localValue}}</span>
</template>
<script>export default{data(){return {localValue:'',}},methods:{onInput(v){//在input事件的处理函数中更新value的绑定值this.localValue=v.target.value;console.log(this.localValue)}}}
</script>

因此当我们修改input输入框中的值时,我们通过v-model绑定的值也会同步修改,基于上述原理,我们可以很容易的实现一个数据双向绑定的组件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/108256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Element el-table 表格内容 样式错乱的问题

表格切换样式错乱展示 因为切换行的高度变化可能未异步渲染 解决方法&#xff1a; 在需要使用v-if渲染的el-table-column元素上加上一个不重复的key值即可解决问题 :key“Math.random()” <el-table-columnprop""label"问题"width"630.5px":…

day35

今日内容概要 Socket抽象层(socket编程) 基于TCP协议的借助socket可以编程客户端和服务端的程序 链接循环 通信循环 基于UDP协议的套接字(socket)编程 粘包现象 如何解决粘包现象(重要的是解决的思路) struct模块的使用(打包、解包) 今日内容详细 Socket抽象层&#x…

Selenium自动化测试总结

一、Selenium自动化测试&#xff08;基于python&#xff09; 1、Selenium简介&#xff1a; 1.1 Selenium是一款主要用于Web应用程序自动化测试的工具集合。Selenium测试直接运行在浏览器中&#xff0c;本质是通过驱动浏览器&#xff0c;模拟浏览器的操作&#xff0c;比如跳转、…

Qt 样式表大全整理

【QT】史上最全最详细的QSS样式表用法及用例说明_qt样式表使用大全_半醒半醉日复日&#xff0c;花落花开年复年的博客-CSDN博客 QT样式表的使用_qt 设置按下 release hover 按钮样式表_create_right的博客-CSDN博客 QPushButton {border-image: url(:/Start_Stop.png); } QPu…

你的助听器装置效果好吗?

作者&#xff1a;兰明 助听效果的好坏是一个多维的概念&#xff0c;简单的讲就是能使听障人士成功地应付生活的程度。影响助听装置效果的因素主要有三个方面&#xff1a;听障人士自身的因素、助听装置本身的因素以及专业服务的因素。其中病史超过半年的听障人士自身的因素&…

【linux】重定向+缓冲区

重定向缓冲区 1.重定向1.1重定向本质1.2重定向接口1.3重定向分类1.3.1>输出重定向1.3.2>>追加重定向1.3.3<输入重定向 2.理解 >&#xff0c; >>&#xff0c; <3.如何理解linux下一切皆文件4.缓冲区4.1理解缓冲区问题4.1.1为什么要有缓冲区4.1.2缓冲区刷…

数据结构:平衡二叉树

平衡二叉树 平衡二叉树&#xff0c;又称为AVL树。实际上就是遵循以下两个特点的二叉树&#xff1a; 每棵子树中的左子树和右子树的深度差不能超过 1&#xff1b;二叉树中每棵子树都要求是平衡二叉树&#xff1b; 其实就是在二叉树的基础上&#xff0c;若树中每棵子树都满足其…

Win10玩游戏老是弹回桌面的解决方法

在Win10电脑中&#xff0c;用户不仅可以办公&#xff0c;也可以畅玩各种各样的游戏。但是&#xff0c;有时候用户在玩游戏的时候&#xff0c;遇到了游戏老是自己弹回桌面的问题&#xff0c;这样是非常影响游戏体验的&#xff0c;却不清楚具体的解决方法。下面小编给大家带来了简…

系统架构与Tomcat的安装和配置

2023.10.16 今天是学习javaweb的第一天&#xff0c;主要学习了系统架构的相关知识和原理&#xff0c;下载了web服务器软件&#xff1a;Tomcat&#xff0c;并对其进行了配置。 系统架构 包括&#xff1a;C/S架构 和 B/S架构。 C/S架构&#xff1a; Client / Server&#xff0…

OJ项目——统一数据格式返回,我是如何处理的?

目录 前言 OJ项目中是如何处理的 1、准备一个类&#xff0c;作为统一的数据返回格式 2、准备一个类&#xff0c;实现ResponseBodyAdvice接口 3、我们如何写返回值更好 4、进一步优化返回值 小结 前言 关于SpringBoot的同一功能处理&#xff0c;本博主在这篇博客已经有介…

MatrixOne Logtail 设计解析

Logtail 是 CN&#xff08;Computation Node&#xff09;与 TN&#xff08;Transaction Node&#xff09;之间的一种日志同步协议&#xff0c;是 CN 和 TN 协同工作的基础。本文将介绍 logtail 协议的基本定位&#xff0c;协议内容和产生过程&#xff0c;也会提及一些遇到的挑战…

如何选择最适合你的LLM优化方法:全面微调、PEFT、提示工程和RAG对比分析

一、前言 自从ChatGPT问世以来&#xff0c;全球各地的企业都迫切希望利用大型语言模型&#xff08;LLMs&#xff09;来提升他们的产品和运营。虽然LLMs具有巨大的潜力&#xff0c;但存在一个问题&#xff1a;即使是最强大的预训练LLM也可能无法直接满足你的特定需求。其原因如…

微信小程序获取手机号(2023年10月 python版)[无需订阅]

技术栈&#xff1a; 1. 微信开发者工具中的调试基础库版本&#xff1a;3.1.2。 2. 后台&#xff1a;django。 步骤&#xff1a; 1. 首先在后台django项目的定时任务中增加一个下载access_token函数&#xff0c;并把得到的access_token保存在数据库中&#xff08;其实随便哪里…

INTELlij IDEA编辑VUE项目

菜单中选择setting–>Plugins 或者快捷键 ctrlalts 搜索vue&#xff0c;但有些情况会搜索不出来&#xff0c;先说搜索到的情况 如下图所示&#xff1a; 如果没有vue.js则说明过去已经安装了。 搜索到了后点击Install安装即可&#xff0c; 但即使搜索成功了&#xff0c;也不…

功夫猫小游戏

欢迎来到程序小院 功夫猫 玩法&#xff1a; 对准对方猫点击鼠标左键进行扑街&#xff0c;碰到敌方猫扑街X1&#xff0c;不要让对方猫碰到自己&#xff0c;统计扑街次数&#xff0c;快去玩功夫猫吧^^。开始游戏https://www.ormcc.com/play/gameStart/189 html <canvas id&q…

非技术背景项目经理如何发展?

非技术背景的项目经理在现代企业中扮演着重要的角色&#xff0c;他们负责协调和管理项目的各个方面&#xff0c;确保项目按时、按预算和按质量要求完成。对于没有技术背景的项目经理来说&#xff0c;他们需要通过一些特定的方法和策略来发展自己的职业生涯。 首先&#xff0c;…

Mac下通过nvm管理node

背景 本地有两个项目&#xff0c;老项目需要用到node 14&#xff0c;新项目需要用node 16&#xff0c;所以只能通过nvm来管理node了 卸载原始的node 我的node是通过官网的.pkg文件安装的&#xff0c;可以通过以下命令进行删除 sudo rm -rf /usr/local/{bin/{node,npm},lib/…

Python数据挖掘入门进阶与实用案例:自动售货机销售数据分析与应用

文章目录 写在前面01 案例背景02 分析目标03 分析过程04 数据预处理1. 清洗数据2.属性选择3.属性规约 05 销售数据可视化分析1.销售额和自动售货机数量的关系2.订单数量和自动售货机数量的关系3.畅销和滞销商品4.自动售货机的销售情况5.订单支付方式占比6.各消费时段的订单用户…

day06-前后端项目上传到gitee、后端多方式登录接口、发送短信功能、发送短信封装、短信验证码接口、短信登录接口

1 前后端项目上传到gitee 2 后端多方式登录接口 2.1 序列化类 2.2 视图类 2.3 路由 3 发送短信功能 4 发送短信封装 4.0 目录结构 4.1 settings.py 4.2 sms.py 5 短信验证码接口 6 短信登录接口 6.1 视图类 6.2 序列化类 1 前后端项目上传到gitee # 我们看到好多开源项目…