MVVM 是 Model-View-ViewModel 缩写,也就是把 MVC 中的 Controller 演变成 ViewModel。Model 层代表数据模型,View 代表 UI 组件,ViewModel 是 View 和 Model 层的桥梁,数据会绑定到 viewModel 层并自动将数据渲染到页面中,视图变化的时候会通知 viewModel 层更新数据。

  1. Model: 代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑。我们可以把 Model 称为数据层,因为它仅仅关注数据本身,不关心任何行为
  2. View: 用户操作界面。当 ViewModel 对 Model 进行更新的时候,会通过数据绑定更新到 View
  3. ViewModel: 业务逻辑层,View 需要什么数据,ViewModel 要提供这个数据;View 有某些操作,ViewModel 就要响应这些操作,所以可以说它是 Model for View.

总结: MVVM 模式简化了界面与业务的依赖,解决了数据频繁更新。MVVM 在使用当中,利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化。

实现一个简易的 MVVM 分为这么几步来

1.类 Vue:这个类接收的是一个 options。
   el属性:根元素的id
data属性:双向绑定的数据。
2.Dep 类:
   subNode数组:存放所依赖这个属性  的依赖,
addSub方法:添加依赖的
removeSub方法:删除,
updata方法:遍历更新它subs中的所有依赖
target静态属性:用来表示 '当前的观察者' ,依赖收集的时候可以将它添加到dep. subNode中 静态的是指向类自身,而不是指向实例对象,主要是归属不同,这是静态属性的核心
3.observe 方法:监听数据变化(参数:data,也就是 options.data)
   遍历data:使用Object.defineProperty()来重写它的get和set,
使用new Dep():实例化一个dep对象,
get时:addSub 添加 '当前的观察者Dep.target' 完成依赖收集,
set时:dep.updata 通知每一个依赖它的观察者进行更新
4.compile 方法:来将 HTML 模版和数据结合起来(参数:node 节点)。
   遍历它的所有子级,判断是否有firstElmentChild
有:进行递归调用compile方法,
没有:child.innderHTML用判断是否有(/\{\{(.*)\}\}/)需要双向绑定的数据,
有:new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm')替换msg变量。
5.将 Dep.target 指向当前的这个 child,
  调用this.opt.data[key]:触发这个数据的get来对当前的child进行依赖收集,
目的:下次数据变化通知child进行视图更新,
将Dep.target指为null(其实在Vue中是有一个targetStack栈用来存放target的指向的)
6.监听 document 的 DOMContentLoaded
   回调函数中实例化这个Vue对象就可以了

需要注意的点:

childNodes 会获取到所有的子节点以及文本节点(包括元素标签中的空白节点)

firstElementChild 表示获取元素的第一个字元素节点,以此来区分是不是元素节点,如果是的话则调用 compile 进行递归调用,否则用正则匹配

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>MVVM</title>
</head>
<body>
<div id="app">
<h3>姓名</h3>
<p>{{name}}</p>
<h3>年龄</h3>
<p>{{age}}</p>
</div>
</body>
</html>
<script>
document.addEventListener(
"DOMContentLoaded",
function () {
let opt = { el: "#app", data: { name: "等待修改...", age: 20 } };
let vm = new Vue(opt);
setTimeout(() => {
opt.data.name = "jing";
}, 2000);
},
false
);
class Vue {
constructor(opt) {
this.opt = opt;
this.observer(opt.data);
let root = document.querySelector(opt.el);
this.compile(root);
}
observer(data) {
Object.keys(data).forEach((key) => {
let obv = new Dep();
data["_" + key] = data[key]; Object.defineProperty(data, key, {
get() {
Dep.target && obv.addSubNode(Dep.target);
return data["_" + key];
},
set(newVal) {
obv.update(newVal);
data["_" + key] = newVal;
},
});
});
}
compile(node) {
[].forEach.call(node.childNodes, (child) => {
if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {
let key = RegExp.$1.trim();
child.innerHTML = child.innerHTML.replace(
new RegExp("\\{\\{\\s*" + key + "\\s*\\}\\}", "gm"),
this.opt.data[key]
);
Dep.target = child;
this.opt.data[key];
Dep.target = null;
} else if (child.firstElementChild) this.compile(child);
});
}
} class Dep {
constructor() {
this.subNode = [];
}
addSubNode(node) {
this.subNode.push(node);
}
update(newVal) {
this.subNode.forEach((node) => {
node.innerHTML = newVal;
});
}
}
</script>

最新文章

  1. javascript深入理解js闭包
  2. 微服务实战(二):使用API Gateway--转
  3. centos apache svn配置
  4. tomcat解决加载JSP文件过大错误
  5. 我心中的核心组件(可插拔的AOP)~调度组件quartz.net续~任务管理器的开发
  6. C++中的迭代器
  7. Android安卓知识点
  8. window.location.href和window.open的几种用法和区别
  9. 6)Java中String类
  10. linux c 分解质因数
  11. [转载]【虚拟化系列】VMware vSphere 5.1 虚拟机管理
  12. mysql 运维常见操作
  13. Kubernetes volumes简介
  14. s遇到错误不要慌,教你方法走四方
  15. ChatGirl 一个基于 TensorFlow Seq2Seq 模型的聊天机器人[中文文档]
  16. multiWriter.go
  17. HDU 6321 Dynamic Graph Matching
  18. AI算法第一天【概述与数学初步】
  19. [MapReduce_3] MapReduce 程序运行流程解析
  20. box-shadow的动效制作

热门文章

  1. Froms
  2. 【GUI开发案例】用python爬百度搜索结果,并开发成exe桌面软件!
  3. Java lambda表达式基本使用
  4. kettel
  5. day21 单列索引与组合索引 &amp; 索引的优点和使用原则 &amp; 视图与函数
  6. 《MySQL必知必会》知识汇总一
  7. 【大数据面试】Flink 04:状态编程与容错机制、Table API、SQL、Flink CEP
  8. ChatGPT 加图数据库 NebulaGraph 预测 2022 世界杯冠军球队
  9. python 数据迁移
  10. DFS深度优先搜索例题分析