class03:MVVM模型与响应式原理
目录
- 一、MVVM模型
- 二、内在
- 1. 深入响应式原理
- 2. Object.entries
- 3. 底层搭建
一、MVVM模型
MVVM,即Model 、View、ViewModel。
Model => data数据
view => 视图(vue模板)
ViewModel => vm => vue 返回的实例 => 控制中心, 负责监听Model的数据,进行改变并且控制视图的更新


vue渲染流程
1. vue 拿到挂载中的所有节点
2. vue 取data中的数据,插入到带有vue指令,特殊的vue符号中
3. vue 将数据插入成功的元素放回到页面中
二、内在
1. 深入响应式原理
如何追踪变化: 当把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty把这些 property 全部转为 getter/setter。
例:监听对象的变化
<script>
// 方法:Object.defineProperty(监听对象,对象的属性名,{配置对象})
let data = {age:38,
}
// 定义一个变量
let _data = null;
Object.defineProperty(data,"age",{get(){console.log("get--data.age取值的时候触发");return _data},set(val){console.log("set--设置data.age的时候触发");_data = val}
})
</script>
使用Object.defineProperty方法,函数内部把属性转化为get/set,get()函数在函数取值时触发,set()函数在设值时触发,通过在外部定义变量,在get()函数内部返回该变量,在set()内部将设的值赋值给该外部变量,从而实现监听。

对象之间的关联:原对象与代理对象
<script>// 关联:代理 let data = {age: 38,}// 定义一个变量let _data = {};Object.defineProperty(_data, "age", {get() {return data.age},set(val) {data.age = val}})
</script>
如上述代码,监听的对象是外部定义的对象,监听的属性名是另一个对象中的属性。在get()执行时,将data.age的值返回给监听对象_data,那么 _data中就会生成一个属性值age: 38,同理在set()执行时,也会将设置的值返回给监听对象 _data,从而修改 _data中的属性值。两个对象data与 _data之间是代理的关系。

vue2底层:数据代理 => 通过一个对象代理另一个对象的中的属性操作(读/写)
2. Object.entries
Object.entries将一级对象处理成键值对的形式。
let data = {name:"Evan You",age:"36",sex:"man"
}

然后通过循环遍历,使用Object.defineProperty方法,把属性转化为get/set,最后代理给_data。
// 原对象
let data = {name:"Evan You",age:36,sex:"man"
}
// 代理对象
let _data = {}
// 处理键值对
Object.entries(data).forEach(([key, val]) => {// 获取data对象的键值对,交给_data代理 createObj(_data, key, val)
})
// 代理
function createObj(_data, key, val){console.log(_data, key, val);Object.defineProperty(_data, key, {// 对data的每一个属性key,get获取值,set将值赋给属性,最后将属性及其对应值赋给监听对象_dataget(){return val;},set(value){val = value;}})
}

修改_data的值,但data的值不会受影响。

3. 底层搭建
创建一个class类Test,返回一个constructor对象,实例化Test。在constructor输出arguments可获得节点和数据。
class Test{constructor(){console.log(arguments);}
}
let vm = new Test({el:"#root",data(){return {num:"32",name:"Jordan",country:"Ame",work:"basketball"}}
})

操作:
<div id="root">{{name}</br><input type="text" v-model="name"> </br>{{work}}
</div>

vue底层:
- vue 拿到挂载中的所有节点;
- vue 拿data中的数据, 插入到带有vue指令,特殊的vue符合中。Object.defineProperty监听data数据;
- vue 将数据插入成功的元素放回到页面中。
class Test {constructor({el, data}) { // 解构赋值// 获取节点this.el = document.querySelector(el);this._data = data;// 调用方法getDom(this.el, this._data)}
}
// 获取节点
function getDom(node, _data){console.log(node, _data);
}

可以通过firstChild和nextSibling获取每一个节点:

getDom函数:
function getDom(node, _data){// console.log(node, _data);// 文本代码拼凑,创建一个新的空白的文档片段let frame = document.createDocumentFragment();let child;console.log(node.firstChild);// 空循环,将赋值给childwhile(child = node.firstChild){// 插入到frameframe.append(child);console.log(child);// child获得节点}return frame;
}
执行结果:页面上的节点被剪切。

循环过程:每执行一次append操作,root的第一个节点就会被剪切掉,当所有节点被剪切掉时为null循环结束。

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ftsYk8AU-1678452135319)(C:\Users\Mamba\AppData\Roaming\Typora\typora-user-images\image-20230224222047687.png)]](https://img-blog.csdnimg.cn/f57efbcaf9664ba9b0185514fe565682.png)
接下来,我们在进入循环之后先调用另一个函数getDom2,判断节点的类型并把原对象的数据取出来赋值给这些节点。
function getDom(node, _data){let frame = document.createDocumentFragment();while(child = node.firstChild){getDom2(child, _data)frame.append(child); }// 返回操作后的节点return frame;
}function getDom2(node, _data){console.log(node, node.nodeType);//正则 匹配插值符号{{}}let reg = /\{\{(.*)\}\}/// 节点// if(noede.nodeType == 1){ // 元素节点,nodeType 属性返回 1 // }if(node.nodeType == 3){ //文本节点,nodeType 属性返回 3// 如果该文本节点匹配到{{}},返回trueif(reg.test(node.nodeValue)){ // 取出节点值,匹配{{}}console.log(reg.test(node.nodeValue)); // 文本节点,返回trueconsole.dir(RegExp);// $1为RegExp的属性,获取{{}}里面的值-> name,worklet arg = RegExp.$1.trim();// 获取{{}}里面的值-> name,workconsole.log(arg);// 将原对象的name,work及其值赋给页面中的{{name}},{{work}}node.nodeValue = _data[arg];// 节点分别为:Jordan、basketballconsole.log(node);// 将数据(节点 data)存储下来 new watcher(_data, node, arg)}}
}class Test {constructor({el, data}) {this.el = document.querySelector(el);this._data=data;// 获取节点this.dom = getDom(this.el, this._data)// 将返回的节点插入页面this.el.append(this.dom)}
}
输出说明:


**说明:**为了方便,html的div中将用于换行的两个删去。
对于元素节点,进行下述处理。
if(node.nodeType == 1){ // 元素节点,nodeType 属性返回 1 console.log(node, node.nodeType);// 获取元素节点上的属性节点console.log(node.attributes);[...node.attributes].forEach((item) => { // 遍历属性节点if(item.nodeName == "v-model"){console.log(item.nodeName);// 获取v-model的属性值-> namelet arg = item.nodeValue;// 双向数据绑定,通过页面修改原对象node.addEventListener("input",(ev)=>{_data[arg] = ev.target.value})console.log(arg);// 将原对象data中的name赋值(代理)到v-model的namenode.value = _data[arg];console.log(node.value);console.log(node);// 将数据(节点 data)存储下来 new watcher(_data, node, arg)}})
}

从vue底层原理可知,在获取节点以及渲染之前,应该先进行数据监听。
数据监听第一步是启动订阅(Dep类),然后调用Object.defineProperty所有属性转为get/set,在get中调用Dep类的addSub方法存储数据,在set中调用Dep类的notify方法修改数据。此时还未获取节点,属于在后端修改数据。
在获取节点的分类节点并插入页面后,调用watcher类,该类先保存数据,并传送数据给Dep类,也进行数据的获取和修改。此时以获取节点,属于在页面修改数据。
以上两部分即是双向数据绑定。
// 订阅发布:在数据变动时,发给订阅者,触发对应的函数
class Dep {constructor() {// 保留数据this.sub = []}addSub(val) {this.sub.push(val)}notify() {this.sub.map((item) => {item.update()})}
}//观察者 => 保存数据,以便后期进行修改
class watcher {constructor(_data, node, arg) {// 数据也给Dep一份,以便Dep订阅数据是否变化Dep.target = this// 保存数据this._data = _data;this.node = node;this.arg = arg;this.init()}init() {// 用于后期进行数据修改this.update()// 修改数据后,清空Dep留存的数据Dep.target = null}update() {// 用于获取数据this.get()// 修改数据this.node.value = this.node.nodeValue = this.value}get() {// 获取数据,数据代理this.value = this._data[this.arg]}
}//处理数据监听
function setdefineProperty(data, _data) { // data===_dataObject.entries(data).forEach(([key, val]) => {createObj(_data, key, val)});
}//监听数据
function createObj(_data, key, val) {//启动 订阅发布let dep = new Dep()// 所有属性转为get/setObject.defineProperty(_data, key, {get() {// 如果Dep.target 有数据if (Dep.target) { //没有数据不就要放进去 dep.addSub(Dep.target)}return val},set(value) {// 数据相同不作改变if (val == value) {return}// 更改数据val = value;// 设置的时候修改页面数据dep.notify()}})
}
双向数据绑定需在获取输入框节点的代码中加入:
// 双向数据绑定,通过页面修改原对象
node.addEventListener("input", (ev) => {_data[arg] = ev.target.value
})

最后,最好将多余的属性和样式删除。如删除v-model。
// 删除属性
node.removeAttribute("v-model")
相关文章:
class03:MVVM模型与响应式原理
目录一、MVVM模型二、内在1. 深入响应式原理2. Object.entries3. 底层搭建一、MVVM模型 MVVM,即Model 、View、ViewModel。 Model > data数据 view > 视图(vue模板) ViewModel > vm > vue 返回的实例 > 控制中心, 负责监听…...
[Spring学习]08 @Resource和@Autowired注解的区别
目录前言一、Resource和Autowired注解的身世1、Resource注解2、Autowired注解3、常见的三种依赖注入方式及区别1. Filed注入2. Setter注入3. Constructor注入4. 三种依赖注入方式的区别二、Resource和Autowired注解的区别三、Resource和Autowired注解的推荐用法前言 当我们在属…...
前端开发神器VS Code安装教程
✅作者简介:CSDN一位小博主,正在学习前端 📃个人主页:白月光777的CSDN博客 💬个人格言:但行好事,莫问前程 安装VS CodeVS Code简介VS Code安装VS Code汉化结束语💡💡&…...
【Hive进阶】-- Hive SQL、Spark SQL和 Hive on Spark SQL
1.Hive SQL 1.1 基本介绍概念Hive由Facebook开发,用于解决海量结构化日志的数据统计,于2008年贡献给 Apache 基金会。Hive是基于Hadoop的数据仓库工具,可以将结构化数据映射为一张表,提供类似SQL语句查询功能本质:将Hi…...
搭建自己的直播流媒体服务器SRS,以及SRS+OBS直播推拉流使用及配置
一、前言 目前,全球直播带货什么的,成为主流,那如何自己搭建一个直播服务器呢。首先需要一个流媒体服务器,搭建流媒体有很多种方式,如下: 流媒体解决方案 Live555 (C)流媒体平台框…...
Node.js-----使用express写接口
使用express写接口 文章目录使用express写接口创建基本的服务器创建API路由模块编写GET接口编写POST接口CROS跨域资源共享1.接口的跨域问题2.使用cros中间件拒绝跨域问题3.什么是cros4.cros的注意事项5.cros请求的分类JSONP接口1.回顾jsonp的概念和特点2.创建jsonp接口的注意事…...
【Linux修炼】16.共享内存
每一个不曾起舞的日子,都是对生命的辜负。 共享内存一.共享内存的原理二.共享内存你的概念2.1 接口认识2.2演示生成key的唯一性2.3 再谈key三.共享资源的查看3.1 如何查看IPC资源3.2 IPC资源的特征3.3 进程之间通过共享内存进行关联四.共享内存的特点五.共享内存的内…...
JAVA进阶 —— Stream流
目录 一、 引言 二、 Stream流概述 三、Stream流的使用步骤 1. 获取Stream流 1.1 单列集合 1.2 双列集合 1.3 数组 1.4 零散数据 2. Stream流的中间方法 3. Stream流的终结方法 四、 练习 1. 数据过滤 2. 数据操作 - 按年龄筛选 3. 数据操作 - 演员信息要求…...
Linux基础命令大全(上)
♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的绽放࿰…...
嵌入式 串口通信
目录 1、通信的基本概念 1.1 串行通信 1.2 并行通信 2、串行通信的特点 2.1 单工 2.2 半双工 2.3 全双工 3、串口在STM32的引脚 4、STM32的串口的接线 4.1 STM32的串口1和电脑通信的接线方式 4.2 单片机和具备串口的设备连接图 5、串口通信协议 6、串口通信…...
C语言函数调用栈
栈溢出(stack overflow)是最常见的二进制漏洞,在介绍栈溢出之前,我们首先需要了解函数调用栈。 函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(caller)和被调用函数…...
【高阶数据结构】红黑树
文章目录1. 使用场景2. 性质3. 结点定义4. 结点旋转5. 结点插入1. 使用场景 Linux进程调度CFSNginx Timer事件管理Epoll事件块的管理 2. 性质 每一个节点是红色或者黑色根节点一定是黑色每个叶子节点是黑色如果一个节点是红色,那么它的两个儿子节点都是黑色从任意…...
网络协议分析期末复习(二)
目录 12. 端口的定义及常见应用对应的端口号 13. UDP协议概述 14.UDP数据报格式及各字段意义 15. UDP-Lite协议概述 16. TCP数据报格式及各字段意义 17. TCP连接建立及协商参数的过程 18. TCP连接释放过程 19. 路由协议分类及各类的具体协议 20. 路由算法常用的度量 2…...
【C++】STL简介 及 string的使用
文章目录1. STL简介1.1 什么是STL1.2 STL的版本1.3 STL的六大组件2. string类的使用2.1 C语言中的字符串2.2 标准库中的string类2.3 string类的常用接口说明1. string类对象的常见构造2. string类对象的容量操作3. string类对象的修改操作4. resize和reserve5. 认识迭代器&…...
MySQL事务详解
🏆今日学习目标: 🍀Spring事务和MySQL事务详解 ✅创作者:林在闪闪发光 ⏰预计时间:30分钟 🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区,欢迎你的加入: …...
ChatGPT背后的技术和多模态异构数据处理的未来展望——我与一位资深工程师的走心探讨
上周,我和一位从业三十余年的工程师聊到ChatGPT。 作为一名人工智能领域研究者,我也一直对对话式大型语言模型非常感兴趣,在讨论中,我向他解释这个技术时,他瞬间被其中惊人之处所吸引🙌,我们深…...
iOS-砸壳篇(两种砸壳方式)
CrackerXI砸壳呢,当时你要是使用 frida-ios-dump 也是可以的; https://github.com/AloneMonkey/frida-ios-dump frida-ios-dump: 代码中需要更改的:手机中的内网ip 密码 等 最后放到我的砸壳路径里: python dump.py -l查看应用…...
linux 基础
1.Shell 命令的格式如下:command -options [argument]command: Shell 命令名称。options: 选项,同一种命令可能有不同的选项,不同的选项其实现的功能不同。argument: Shell 命令是可以带参数的,也可以不带参…...
Java:SpringBoot给Controller添加统一路由前缀
网上的文章五花八门,不写SpringBoot的版本号,导致代码拿来主义不好使了。 本文采用的版本 SpringBoot 2.7.7 Java 1.8目录1、默认访问路径2、整个项目增加路由前缀3、通过注解方式增加路由前缀4、按照目录结构添加前缀参考文章1、默认访问路径 packag…...
Java 基于 JAVE 库 实现 视频转音频的批量转换
文章目录 Java 基于 JAVE 库 实现 视频转音频的批量转换Maven:方案一:代码优化:方案二:示例代码:代码优化:结语Java 基于 JAVE 库 实现 视频转音频的批量转换 实现视频转音频的功能需要使用到一个第三方的 Java 库,叫做 JAVE。JAVE 是一个开源的 Java 库,提供了视频和音频转换…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
