class03:MVVM模型与响应式原理
创始人
2024-06-01 09:51:42
0

目录

  • 一、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。

例:监听对象的变化


使用Object.defineProperty方法,函数内部把属性转化为get/set,get()函数在函数取值时触发,set()函数在设值时触发,通过在外部定义变量,在get()函数内部返回该变量,在set()内部将设的值赋值给该外部变量,从而实现监听。

对象之间的关联:原对象与代理对象


如上述代码,监听的对象是外部定义的对象,监听的属性名是另一个对象中的属性。在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"}}
})    

操作

{{name}

{{work}}

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")

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
AWSECS:哪种网络模式具有... 使用AWS ECS中的awsvpc网络模式来获得最佳性能。awsvpc网络模式允许ECS任务直接在V...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...