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将值赋给属性,最后将属性及其对应值赋给监听对象_data
        get(){
            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);
    // 空循环,将赋值给child
    while(child = node.firstChild){
        // 插入到frame
        frame.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
        // 如果该文本节点匹配到{{}},返回true
        if(reg.test(node.nodeValue)){  // 取出节点值,匹配{{}}
            console.log(reg.test(node.nodeValue)); // 文本节点,返回true
            console.dir(RegExp);
            // $1为RegExp的属性,获取{{}}里面的值-> name,work
            let arg = RegExp.$1.trim();
            // 获取{{}}里面的值-> name,work
            console.log(arg);
            // 将原对象的name,work及其值赋给页面中的{{name}},{{work}}
            node.nodeValue = _data[arg];
            // 节点分别为:Jordan、basketball
            console.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的属性值-> name
            let arg = item.nodeValue;
            // 双向数据绑定,通过页面修改原对象
            node.addEventListener("input",(ev)=>{
            	_data[arg] = ev.target.value
            })
            console.log(arg);
            // 将原对象data中的name赋值(代理)到v-model的name
            node.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===_data
    Object.entries(data).forEach(([key, val]) => {
        createObj(_data, key, val)
    });
}

//监听数据
function createObj(_data, key, val) {
    //启动 订阅发布
    let dep = new Dep()
    // 所有属性转为get/set
    Object.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")

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT使用介绍、ChatGPT+编程、相关组件和插件记录

文章目录介绍认识ChatGPT是通过英汉互译来实现中文回答的吗同一个问题&#xff0c;为什么中英文回答不同ChatGPT的使用对话组OpenAI APIAI智能绘图DALLE 2ChatGPT for Google插件ChatGPT编程编写代码代码错误修正与功能解读代码评审与优化推荐技术方案编写和优化SQL语句在代码编…

Spring Boot集成RocketMQ实现普通、延时、事务消息发送接收、PULL消费模式及开启ACL | Spring Cloud 30

一、前言 在前面我们通过以下章节对RocketMQ有了基础的了解&#xff1a; docker-compose 搭建RocketMQ 5.1.0 集群&#xff08;双主双从模式&#xff09; | Spring Cloud 28 docker-compose 搭建RocketMQ 5.1.0 集群开启ACL权限控制 | Spring Cloud 29 现在开始我们正式学习…

蓝桥杯 - 求组合数【C(a,b)】+ 卡特兰数

文章目录&#x1f4ac;前言885. 求组合数 I C(m,n) 【dp】886 求组合数 II 【数据大小10万级别】 【费马小定理快速幂逆元】887. 求组合数 III 【le18级别】 【卢卡斯定理 逆元 快速幂 】888.求组合数 IV 【没有%p -- 高精度算出准确结果】 【分解质因数 高精度乘法 --只用一…

5.5G产业再提速!高通5GAdvanced-ready芯片商用终端下半年面世

MWC2023大会召开在即&#xff0c;5GAdvanced产业再添重磅消息&#xff01;2月15日&#xff0c;高通宣布推出全球首个5GAdvanced-ready基带芯片——骁龙X755G调制解调器及射频系统&#xff0c;支持毫米波和Sub-6GHz频段&#xff0c;带来网络覆盖、时延、能效和移动性等全方位的提…

【C语言】深度理解指针(下)

一. 前言&#x1f48e;昨晚整理博客时突然发现指针还少了一篇没写&#xff0c;今天就顺便来补一补。上回书说到&#xff0c;emmm忘记了&#xff0c;没事&#xff0c;我们直接进入本期的内容:本期我们带来了几道指针相关笔试题的解析&#xff0c;还算是相对比较轻松的。话不多说…

RTL8201 以太网PHY芯片 调试记录

一、概述 为了尽量给甲方降低成本&#xff0c;决定使用较低成本的PHY芯片RTL8201F-VB-CG芯片。移植官网的以太网demo程序&#xff0c;git上下载了一份很好看的rtl8201F的驱动程序&#xff0c;用来替换官方demo的lan8742程序。并没有直接通&#xff0c;于是开始了调试之路。 二…

mysql索引类型有哪些?

在Mysql数据库当中&#xff0c;我们经常会谈到Sql语句&#xff0c;当然也会谈到索引优化&#xff0c;那么在数据库当中有哪些索引类型呢&#xff0c;博主在这里进行分享&#xff0c;希望对大家能有所帮助。 目录 1、B-Tree索引&#xff1a; 2、Hash索引&#xff1a; 3、Full…

SpringBoot 将PDF转成图片或World

SpringBoot 将PDF转成图片或World 准备工作Apache PDFBox将PDF转成一张图片将PDF转成多张图片将PDF转成其他文件格式总结SpringBoot 是一款非常流行的 Java Web 开发框架,可以用来构建各种 Web 应用程序。在本篇博客中,我们将介绍如何使用 SpringBoot 将 PDF 转换成图片或其他…

Elasticsearch 学习+SpringBoot实战教程(三)

需要学习基础的可参照这两文章 Elasticsearch 学习SpringBoot实战教程&#xff08;一&#xff09; Elasticsearch 学习SpringBoot实战教程&#xff08;一&#xff09;_桂亭亭的博客-CSDN博客 Elasticsearch 学习SpringBoot实战教程&#xff08;二&#xff09; Elasticsearch …

第十四届蓝桥杯三月真题刷题训练——第 23 天

目录 第 1 题&#xff1a;长草 题目描述 输入描述 输出描述 输入输出样例 运行限制 代码&#xff1a; 思路&#xff1a; 第 2 题&#xff1a;蓝肽子序列_LCS_最长公共子序列dp问题 题目描述 输入描述 输出描述 输入输出样例 运行限制 代码&#xff1a; 思路&am…

Spring源码面试最难问题——循环依赖

前言 问&#xff1a;Spring 如何解决循环依赖&#xff1f; 答&#xff1a;Spring 通过提前曝光机制&#xff0c;利用三级缓存解决循环依赖&#xff08;这原理还是挺简单的&#xff0c;参考&#xff1a;三级缓存、图解循环依赖原理&#xff09; 再问&#xff1a;Spring 通过提前…

【前沿技术】问答pk【ChatGPT Vs Notion AI Vs BAT AI 】

目录 写在前面 问题&#xff1a; 1 ChatGPT 1.1 截图 ​1.2 文字版 2 Notion AI 2.1 截图 2.2 文字版 3 BAT AI 3.1 截图 3.2 文字版 总结 序言 所有幸运和巧合的事&#xff0c;要么是上天注定&#xff0c;要么是一个人偷偷的在努力。 突发奇想&#xff0c;问三个…

③【Java组】蓝桥杯省赛真题 持续更新中...

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 蓝桥杯真题--持续更新中...一、错误票据题目描…

CCF-CSP认证 202303 500分题解

202303-1 田地丈量&#xff08;矩阵面积交&#xff09; 矩阵面积交x轴线段交长度*y轴线段交长度 线段交长度&#xff0c;相交的时候是min右端点-max左端点&#xff0c;不相交的时候是0 #include<bits/stdc.h> using namespace std; int n,a,b,ans,x,y,x2,y2; int f(in…

用CSS3画了一只猫

感觉我写得技术含量不高&#xff0c;全都是用绝对定位写的&#xff0c;一定会有更好的&#xff0c;代码量更少的做法吧 <!DOCTYPE html> <html> <head><title>Cute Cat</title><style type"text/css">*{box-sizing: border-box…

100天精通Python(可视化篇)——第81天:matplotlib绘制不同种类炫酷饼图参数说明+代码实战(自定义、百分比、多个子图、圆环、嵌套饼图)

文章目录专栏导读0. 前言1. 参数说明2. 普通饼图3. 百分比饼图4. 突出某一块的饼图5. 自定义颜色的饼图6. 多个子图7. 圆环饼图8. 圆环分离饼图9. 饼图圆环图组合10. 多层圆环饼图专栏导读 &#x1f525;&#x1f525;本文已收录于《100天精通Python从入门到就业》&#xff1a…

【VScode】远程连接Linux

目录标题1. 安装扩展插件2. 在Linux上操作3. 确定Linux的IP地址4. 远程连接到Linux5. 实现免密码登录使用 VScode 远程编程与调试的时有会用到插件 Remote Development&#xff0c;使用这个插件可以在很多情况下代替 vim 直接远程修改与调试服务器上的代码&#xff0c;同时具备…

超详细讲解C语言文件操作!!

超详细讲解C语言文件操作&#xff01;&#xff01;什么是文件文件名文件的打开和关闭文件指针文件的打开和关闭文件的顺序读写文件的随机读写fseekftellrewind文本文件和二进制文件文件读取结束的判定文件缓冲区什么是文件 磁盘上的文件是文件。但是在程序设计中&#xff0c;我…

Python | 蓝桥杯系列文章总结+经典例题重做

欢迎交流学习~~ 专栏&#xff1a; 蓝桥杯Python组刷题日寄 从 4 个月前开始写蓝桥杯系列&#xff0c;到目前为止一共是 19 篇&#xff0c;其中&#xff1a;入门篇 5 篇&#xff0c;简单篇 8 篇&#xff0c;进阶篇 6 篇。 这篇文章主要是为了为先前内容进行总结&#xff0c;并对…

蓝桥杯冲刺 - Lastweek - 你离省一仅剩一步之遥!!!(掌握【DP】冲刺国赛)

文章目录&#x1f4ac;前言&#x1f3af;week3&#x1f332;day10-1背包完全背包多重背包多重背包 II分组背包&#x1f332;day2数字三角形 - 线性DP1015. 摘花生 - 数字三角形&#x1f332;day3最长上升子序列 - 线性DP1017. 怪盗基德的滑翔翼 - LIS1014.登山 - LIS最长公共子…