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

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底层:

  1. vue 拿到挂载中的所有节点;
  2. vue 拿data中的数据, 插入到带有vue指令,特殊的vue符合中。Object.defineProperty监听data数据;
  3. 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)]

接下来,我们在进入循环之后先调用另一个函数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&#xff0c;即Model 、View、ViewModel。 Model > data数据 view > 视图&#xff08;vue模板&#xff09; 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安装教程

✅作者简介&#xff1a;CSDN一位小博主&#xff0c;正在学习前端 &#x1f4c3;个人主页&#xff1a;白月光777的CSDN博客 &#x1f4ac;个人格言&#xff1a;但行好事&#xff0c;莫问前程 安装VS CodeVS Code简介VS Code安装VS Code汉化结束语&#x1f4a1;&#x1f4a1;&…...

【Hive进阶】-- Hive SQL、Spark SQL和 Hive on Spark SQL

1.Hive SQL 1.1 基本介绍概念Hive由Facebook开发&#xff0c;用于解决海量结构化日志的数据统计&#xff0c;于2008年贡献给 Apache 基金会。Hive是基于Hadoop的数据仓库工具&#xff0c;可以将结构化数据映射为一张表&#xff0c;提供类似SQL语句查询功能本质&#xff1a;将Hi…...

搭建自己的直播流媒体服务器SRS,以及SRS+OBS直播推拉流使用及配置

一、前言 目前&#xff0c;全球直播带货什么的&#xff0c;成为主流&#xff0c;那如何自己搭建一个直播服务器呢。首先需要一个流媒体服务器&#xff0c;搭建流媒体有很多种方式&#xff0c;如下&#xff1a; 流媒体解决方案 Live555 &#xff08;C&#xff09;流媒体平台框…...

Node.js-----使用express写接口

使用express写接口 文章目录使用express写接口创建基本的服务器创建API路由模块编写GET接口编写POST接口CROS跨域资源共享1.接口的跨域问题2.使用cros中间件拒绝跨域问题3.什么是cros4.cros的注意事项5.cros请求的分类JSONP接口1.回顾jsonp的概念和特点2.创建jsonp接口的注意事…...

【Linux修炼】16.共享内存

每一个不曾起舞的日子&#xff0c;都是对生命的辜负。 共享内存一.共享内存的原理二.共享内存你的概念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基础命令大全(上)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…...

嵌入式 串口通信

目录 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语言函数调用栈

栈溢出&#xff08;stack overflow&#xff09;是最常见的二进制漏洞&#xff0c;在介绍栈溢出之前&#xff0c;我们首先需要了解函数调用栈。 函数调用栈是一块连续的用来保存函数运行状态的内存区域&#xff0c;调用函数&#xff08;caller&#xff09;和被调用函数&#xf…...

【高阶数据结构】红黑树

文章目录1. 使用场景2. 性质3. 结点定义4. 结点旋转5. 结点插入1. 使用场景 Linux进程调度CFSNginx Timer事件管理Epoll事件块的管理 2. 性质 每一个节点是红色或者黑色根节点一定是黑色每个叶子节点是黑色如果一个节点是红色&#xff0c;那么它的两个儿子节点都是黑色从任意…...

网络协议分析期末复习(二)

目录 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事务详解

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;Spring事务和MySQL事务详解 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: …...

ChatGPT背后的技术和多模态异构数据处理的未来展望——我与一位资深工程师的走心探讨

上周&#xff0c;我和一位从业三十余年的工程师聊到ChatGPT。 作为一名人工智能领域研究者&#xff0c;我也一直对对话式大型语言模型非常感兴趣&#xff0c;在讨论中&#xff0c;我向他解释这个技术时&#xff0c;他瞬间被其中惊人之处所吸引&#x1f64c;&#xff0c;我们深…...

iOS-砸壳篇(两种砸壳方式)

CrackerXI砸壳呢&#xff0c;当时你要是使用 frida-ios-dump 也是可以的&#xff1b; https://github.com/AloneMonkey/frida-ios-dump frida-ios-dump: 代码中需要更改的&#xff1a;手机中的内网ip 密码 等 最后放到我的砸壳路径里&#xff1a; python dump.py -l查看应用…...

linux 基础

1.Shell 命令的格式如下&#xff1a;command -options [argument]command: Shell 命令名称。options&#xff1a; 选项&#xff0c;同一种命令可能有不同的选项&#xff0c;不同的选项其实现的功能不同。argument&#xff1a; Shell 命令是可以带参数的&#xff0c;也可以不带参…...

Java:SpringBoot给Controller添加统一路由前缀

网上的文章五花八门&#xff0c;不写SpringBoot的版本号&#xff0c;导致代码拿来主义不好使了。 本文采用的版本 SpringBoot 2.7.7 Java 1.8目录1、默认访问路径2、整个项目增加路由前缀3、通过注解方式增加路由前缀4、按照目录结构添加前缀参考文章1、默认访问路径 packag…...

Java 基于 JAVE 库 实现 视频转音频的批量转换

文章目录 Java 基于 JAVE 库 实现 视频转音频的批量转换Maven:方案一:代码优化:方案二:示例代码:代码优化:结语Java 基于 JAVE 库 实现 视频转音频的批量转换 实现视频转音频的功能需要使用到一个第三方的 Java 库,叫做 JAVE。JAVE 是一个开源的 Java 库,提供了视频和音频转换…...

告别手动配网!用IEEE 1905.1协议实现Wi-Fi AP自动配置的保姆级流程拆解

告别手动配网&#xff01;用IEEE 1905.1协议实现Wi-Fi AP自动配置的保姆级流程拆解 想象一下&#xff0c;当你需要为三层别墅部署全屋Wi-Fi覆盖&#xff0c;或是为小型办公室搭建多AP无线网络时&#xff0c;传统方式需要逐个登录每个AP的后台&#xff0c;重复输入SSID、密码、…...

Sketch Find and Replace终极指南:设计师必备的批量文本替换神器

Sketch Find and Replace终极指南&#xff1a;设计师必备的批量文本替换神器 【免费下载链接】Sketch-Find-And-Replace Sketch plugin to do a find and replace on text within layers 项目地址: https://gitcode.com/gh_mirrors/sk/Sketch-Find-And-Replace 还在为Sk…...

从继电器到边缘计算:拆解PAC控制器里的‘智能手机’架构(以Codesys/倍福为例)

从继电器到边缘计算&#xff1a;拆解PAC控制器里的‘智能手机’架构 在工业自动化领域&#xff0c;PAC&#xff08;可编程自动化控制器&#xff09;正逐渐取代传统PLC&#xff0c;成为智能制造的核心大脑。这种转变类似于功能手机向智能手机的进化——从单一功能到开放平台&…...

解决ClaudeCode频繁封号与Token不足的Taotoken替代方案

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 解决ClaudeCode频繁封号与Token不足的Taotoken替代方案 对于依赖Claude Code这类编程助手工具的开发者而言&#xff0c;访问不稳定…...

容器化技术从入门到精通:Docker与Kubernetes实战指南

1. 项目概述&#xff1a;从零到一构建容器化认知体系最近在技术社区里&#xff0c;经常看到有朋友在讨论“stephrobert/containers-training”这个项目。乍一看&#xff0c;这像是一个关于容器技术的培训或学习资料库。作为一个在云原生和容器化领域摸爬滚打了多年的从业者&…...

放心API和4SAPI怎么选?从开发者选型角度看差异

很多开发者在选 Claude API 中转站时&#xff0c;都会遇到一个问题&#xff1a;**到底是选更偏个人友好的放心API&#xff0c;还是选更偏企业级的4SAPI&#xff1f;**这个问题没有标准答案&#xff0c;只有场景答案。---## 一、先给结论如果你的项目处于以下阶段&#xff1a;- …...

TI C2000 DSP入门新姿势:Simulink硬件支持包安装与CCS v10.1.0联调实战记录

TI C2000 DSP开发环境搭建&#xff1a;从Simulink支持包到CCS联调全指南 当第一次打开Matlab准备为C2000 DSP开发算法时&#xff0c;很多人会惊讶地发现&#xff1a;明明安装了CCS和Matlab&#xff0c;却无法直接在Simulink中找到C2000的硬件支持。这不是个例——根据TI官方论坛…...

超导输电技术:从原理到工程应用的挑战与前景

1. 超导输电线路&#xff1a;从技术神话到工程现实的漫长跋涉大约二十年前&#xff0c;当“高温超导”这个名词开始从实验室走向产业界的视野时&#xff0c;整个电力工程领域都为之振奋。想象一下&#xff0c;我们日常依赖的庞大电网&#xff0c;其输电线路中高达5%到10%的电能…...

大模型应用开发,常用框架汇总

大模型应用开发所涉及的工具和框架&#xff0c;非常的多&#xff0c;且技术更新非常之快。很难全面梳理技术栈全景图。 上一期文章&#xff0c;按照六层框架梳理了全景图&#xff0c;本期文章又收集了一些零散的信息&#xff0c;可以对上一期的架构图各个层级&#xff0c;做个补…...

高速PCB设计:信号完整性与电磁场思维实战解析

1. 高速PCB设计的核心挑战与设计思维转变十年前我刚接触高速PCB设计时&#xff0c;曾天真地认为只要把线连通就能工作。直到某次设计的DDR3内存模块在800MHz频率下频繁出错&#xff0c;才真正理解到&#xff1a;当信号上升时间进入亚纳秒级&#xff0c;PCB上的每毫米走线都成为…...