实现一个简易的 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. 算法是什么我记不住,But i do it my way. 解一道滴滴出行秋招编程题。
  2. html5手机常见问题与工具分享
  3. Hibernate配置文件
  4. hdu----(5047)Sawtooth(大数相乘+数学推导)
  5. ulink 固件更新问题
  6. java 多线程 CountDownLatch用法
  7. duilib中控件拖拽功能的实现方法(附源码)
  8. Windows xp下IDT Hook和GDT的学习
  9. C# 中的 lock的陷阱
  10. Selenium如何实现dropbar移动
  11. 在Linux上怎么安装和配置DenyHosts工具
  12. 面试之get和post(转)
  13. Zoj 3842 Beauty of Array
  14. set echo on/off,set term on/off,set feedback off,set heading off命令(转)
  15. 一、Python3.6+PyQt5 安装
  16. LeetCode算法题-Keyboard Row(Java实现)
  17. lwip TCP client &amp; FreeRTOS 打开TCP 的 保活机制 LWIP_TCP_KEEPALIVE==1
  18. Python全栈开发之路 【第十五篇】:jQuery的介绍和选择器
  19. jquery easyui datagrid 空白条处理 自适应宽高 格式化函数formmater 初始化时会报错 cannot read property &#39;width&#39;||&#39;length&#39; of null|undefined
  20. Vue学习二:v-model指令使用方法

热门文章

  1. Go语言核心36讲18
  2. Django 接收到body后 json.loads() 报编码错误 且在报错之前打印body为空
  3. 【云原生 · Kubernetes】apiserver高可用
  4. 认证服务(keystone)
  5. 螺旋矩阵II-LeetCode59 考验代码能力
  6. 【数据库】PostgreSQL/PgSql-根据模式名和字段名查询有该字段的所有表信息【通过表元数据信息和函数实现】
  7. Django批量插入(自定义分页器)
  8. 重学c#系列——linq(2) [二十八]
  9. PyTorch复现GoogleNet学习笔记
  10. Django 连接各数据库配置汇总(sqlite3,MySql,Oracle)