MVVM框架原理浅谈

news/2024/7/3 3:19:15

MVVM基本原理

MVVM(Model-View-ViewModel)本质上就是MVC 的改进版,MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

数据修改通知ViewModel
通知Model数据修改
View数据修改通知ViewModel
ViewModel数据修改通知View
Model
ViewModel
View

MVVM相比与MVC模式主要是分离了试图和模型,所有的交互都通过ViewModel进行了数据的通知与交互,从而达到了低耦合的优点,View的修改可独立于Model的修改,可提高重用性,可在View中重用独立的视图逻辑。归根结底可总结为数据的双向绑定的过程,即ViewModel的数据与Model的绑定,ViewModel的数据与View中的数据绑定,从而达到数据低耦合高重用性的过程。

MVVM的原理的基础

MVVM的基础模式其实不复杂,通过该原理主要就是需要实现ViewModel层到Model层与View层的交互过程。在Vue的官方示例图中,给出了如下图示;

在这里插入图片描述

从图示所致,DOM就是视图,Model就是JS对象,Vue就是作为ViewModel存在将双向的数据进行绑定,那如何来实现一个简单的双向数据绑定呢?

MVVM的流程梳理

根据js的相关内容来实现的思考,基本流程如下;
在这里插入图片描述
大致的思路逻辑如上所示,其中在Model修改数据的时候主要就是通过addEventListener事件来注册回调函数,ViewModel到View的事件或者View到ViewModel的事件主要就是通过Object.defineProperty属性来设置值。

假如数据格式如下;

视图(View)
<input v-model="c" type="text">  MVVM框架(ViewModel)
将视图的内容和数据当做参数传入Mvvm中生成一个实例数据(Model)
{c: 2}
MVVM实例初始化过程
  1. 首先,根据传入的Model,调用Object.defineProperty来劫持数据,并设置set和get方法,并注册回调watch更新视图函数
  2. 通过传入的视图信息编译初始化一个视图,并根据传入的编译后的视图添加监听函数,监听html中输入输入的响应事件,如果触发则回调设置到Mvvm中的对应的实例值
  3. 如果是通过其他事件修改了Model的值则执行注册的watch方法,重新生成对应的视图内容,渲染出新的页面值,从而完成从数据到视图的更新

以上,就基本是MVVM框架中从Model层到View层,和View层到Model层的数据的交流的概述流程。

实现简易的MVVM框架
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app"><h1>{{singer}}</h1>                       <!-- 模板渲染 --><h1>{{song.first}}</h1>                   <!--  递归嵌套 --><input v-model="singer" type="text">      <!--  双向绑定 --></div>
</body>
<script >function Mvvm(options = {}) {// vm.$options Vue上是将所有属性挂载到上面this.$options = options;let data = this._data = this.$options.data;this.init()                     // 初始化设置熟悉// 数据劫持observe(data);new Compile(options.el, this)   // 编译内容}// Dep 订阅发布模式实现类function Dep() {// 一个数组(存放函数的事件池)this.subs = [];}Dep.prototype.addSub = function(sub){this.subs.push(sub);},Dep.prototype.notify = function(val) {// 绑定的方法,都有一个update方法this.subs.forEach(sub => sub.update(val));}// 数据劫持function observe(data){// 如果传入为空或者不是object则返回if (!data || typeof data !== "object"){return}for (let key in data){let dep = new Dep()let val = data[key]observe(val)      // 嵌套调用Object.defineProperty(data, key, {configurable: true,get() {// 通过该函数注册回调函数到depif (Dep.target){dep.addSub(Dep.target)}return val;},set(newVal) {if (val === newVal) {return;}// 更新回调视图val = newVal;dep.notify(newVal)}})}}Mvvm.prototype = {init() {for (let key in this._data){Object.defineProperty(this, key, {configurable: true,get() {return this._data[key];},set(newVal) {this._data[key] = newVal;}})}}}function Compile(el, vm){vm.$el = document.querySelector(el);// 在el范围里将内容都拿到,当然不能一个一个的拿let fragment = document.createDocumentFragment();while (child = vm.$el.firstChild) {fragment.appendChild(child);    // 此时将el中的内容放入内存中}// 对el里面的内容进行替换function replace(frag) {Array.from(frag.childNodes).forEach(node => {let txt = node.textContent;let reg = /\{\{(.*?)\}\}/g;   // 正则匹配{{}}if (node.nodeType === 3 && reg.test(txt)) { // 即是文本节点又有大括号的情况{{}}let arr = RegExp.$1.split('.');   // 匹配到的第一个分组 如: a.b, clet val = vm;arr.forEach(key => {val = val[key];});let w = new Watcher(vm, RegExp.$1, function(newVal) {node.textContent = newVal;   // 当watcher触发时会自动将内容放进输入框中});node.textContent = txt.replace(reg, val).trim();}if (node.nodeType == 1) {// 检查是否是v-model属性值let nodeAttr = node.attributesArray.from(nodeAttr).forEach(attr => {let name = attr.name;   // v-model  typelet exp = attr.value;   // data keyif (name == "v-model"){node.value = vm[exp]let w = new Watcher(vm, exp, function(newVal) {node.value = vm[exp];   // 当watcher触发时会自动将内容放进输入框中});}node.addEventListener('input', e => {let newVal = e.target.value;// 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新vm[exp] = newVal;});})}if (node.childNodes && node.childNodes.length) {replace(node);}});}replace(fragment);  // 替换内容vm.$el.appendChild(fragment);   // 再将文档碎片放入el中}// 监听函数function Watcher(vm, exp, fn) {this.vm = vmthis.exp = expthis.fn = fn;   // 将fn放到实例上let arr = exp.split('.');   // 检查是否是深层次递归嵌套let data = this.vm._dataif (arr.length > 1){let length = arr.lengthfor(i=0;i<length-1;i++){data = data[arr[i]]}// 只注册最后一个回调函数Dep.target = thisconst value = data[arr[length-1]]} else {Dep.target = thisconst value = data[exp]}Dep.target = null}Watcher.prototype.update = function(val){this.fn(val)};let app = new Mvvm({el: "#app",data: {"singer": "singer1", "song": {"first": "contry", "second": "home"}}})</script>
</html>

该段代码运行在浏览器之后,在输入框中输入数据,可看到第一行的数据也会跟着改变,此时打开浏览器调试窗口的终端,

在这里插入图片描述

从终端打印信息可看出,基本都是实现了set get方法,并且会根据数据的改变从而改变视图。

本段代码参考了网上一些实现的代码,并做了修改,代码的实现思路跟上图中的流程图相似。在实现的过程中也借用了闭包的原理,为每一个属性值在defineProperty的时候,创建一个订阅数组dep,然后当数据有更改之后,就通知所有订阅该dep的所有接受着,这样确保每次的数据修改都只修改到订阅的局部的订阅者。

劫持数据
get
set
编译文件
编译到匹配的key
newMvvm
defineProperty
添加watcher中的订阅者到dep
调用dep中的通知函数去更新视图
编译
生成一个watcher实例并调用该属性的get方法注册到dep中

主要做的两件事情就是将数据进行双向的绑定,具体的实现大家可自行深入学习,本文的示例代码基于网上实例进行修改,原文来自不好意思!耽误你的十分钟,让MVVM原理还给你。

总结

本文主要就是简单的探究了一下MVVM框架的基本原理,仅仅是作为在使用vue框架的时候的基础回顾,原理并不算复杂,主要就是通过数据的双向绑定从而提高开发效率与代码复用率。由于本人才疏学浅,如有错误请批评指正。


http://lihuaxi.xjx100.cn/news/257819.html

相关文章

Spring MVC 五大组件

欢迎关注方志朋的博客&#xff0c;回复”666“获面试宝典是一个MVC架构&#xff0c;用来简化基于MVC架构的Web应用开发。SpringMVC最重要的就是五大组件1. DispatcherServlet2. HandleMapping3. Controller4. ModeAndView5. ViewResolver下面一一介绍这五大控件1. DispatcherSe…

max(min)-device-width和max(min)-width的区别

max-device-width和max-width的区别表现在如下几个方面&#xff1a; 1. max-device-width是设备整个显示区域的宽度&#xff0c;例如&#xff0c;真实的设备屏幕宽度。 2. max-width是目标显示区域的宽度&#xff0c;例如&#xff0c;浏览器宽度。 3. 如果使用max-device-width…

磁盘及文件系统管理详解

2019独角兽企业重金招聘Python工程师标准>>> 大纲 一、硬盘物理结构及相关结构 二、硬盘逻辑结构及相关概念 三、Ext2文件系统逻辑结构 四、读取、创建、删除、复制、剪切过程 五、软链接与硬链接联系与区别 六、虚拟文件系统 六、文件系统管理相关命令 一、硬盘物理…

西瓜书公式推导讲解来了!

↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习&#xff0c;不错过 Datawhale学习 开源贡献&#xff1a;Datawhale团队 组队学习 Datawhale南瓜书是经典机器学习教材《机器学习》&#xff08;西瓜书&#xff09;的公式推导解析指南&#xff0c;旨在让在学…

AI也有健忘症?英国41岁教授专访:解决灾难性遗忘

视学算法报道 编辑&#xff1a;Joey 好困【导读】罗切斯特大学计算机科学家在持续学习领域的开创性研究&#xff0c;有望解决算法一直以来存在的灾难性遗忘问题。如何实现持续学习&#xff0c;让AI不断片&#xff1f;近日&#xff0c;来自罗切斯特大学的41岁计算机科学家Chris…

几个我收藏的经典网站

壁纸酷:[url]http://www.bizhiku.net[/url],XP壁纸,vista壁纸,汽车壁纸,风景壁纸,游戏壁纸,美女桌面壁纸,3d壁纸,日历月历壁纸等等,精美桌面壁纸下载.WAP中国:[url]http://www.wapcn.net[/url],专业的手机资源网站下载,手机铃声,手机游戏,手机壁纸,手机电影,手机软件,手机主题下…

Matlab与线性代数--广义逆矩阵

本微信图文详细介绍了Matlab中求解广义逆矩阵的方法。

有哪些新手程序员不知道的小技巧?

提到新手程序员&#xff0c;大家想到的第一个词可能就是&#xff1a;刷题。尤其是通过LeetCode刷题&#xff0c;想必新手程序员们都经历过这一步&#xff0c;甚至不少人认为只要在LeetCode上刷的题目够多&#xff0c;就一定能够进阶为大神。但是&#xff0c;不难发现&#xff0…