<!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>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="message">
{{message}}
</div>
<!-- 最后展示给用户看的视图 -->
<!-- ===> -->
<!-- <input type="text" value="hyj">
hyj -->
</body>
function Mvvm({el, $data}) {
// 获取到了根元素
this.root = document.querySelector(el);
// 挂载了数据
this.$data = $data;
// 劫持和监听数据
observer(this.$data, this); // 将数据和实例给监听者
// 编译模板, 编译完成之后, 返回DOM片段,才是展示给用户看的页面,
let fragment = createFragment(this.root, this);
// 重新放入到根元素中
this.root.appendChild(fragment);
}
function createFragment(root, vm) { // root: 根节点, vm: 实例
// 先默认指定第一个节点
let child = root.firstChild;
// 创建一个dom片段
let fragment = document.createDocumentFragment();
while (child) {
// 拿到每一个子节点,然后编译一遍
compile(child, vm);
// 编译完成之后放入DOM片段中
fragment.appendChild(child);
// 编译完一个就重置一个
child = root.firstChild;
}
return fragment;
}
// 编译解析函数
function compile(node, vm) {
// 判断节点类型: 如果是1的话说明是元素,然后我们只考虑数据双向绑定,所以采用v-model + 单行文本输入框
if (node.nodeType === 1) {
let attrs = node.attributes;
// 获取v-model上的变量名
let vModelVal = attrs['v-model'].value;
node.addEventListener('input', function (e) {
vm[vModelVal] = e.target.value;
})
node.value = vm[vModelVal];
new Watch(node, vModelVal, vm);
}
// 文本节点为3
if (node.nodeType === 3) {
// 获取文本值
let textCon = node.textContent;
let reg = /\{\{(.*)\}\}/g;
if (reg.test(textCon)) {
new Watch(node, RegExp.$1, vm);
}
}
}
// 监听者
function observer(data, vm) {
Object.keys(data).forEach(k => {
// 每一个数据属性都做劫持,并劫持在vm上
defineProps(data[k], k, vm);
});
}
// 劫持
function defineProps(val, k, vm) {
// 表面上: 是定义给了vm实例上属性,实际上是将值放在vm实例$data里面
let dep = new Dep();
Object.defineProperty(vm, k, {
// 做设置的劫持
set(newVal) {
if (val === newVal) return;
vm.$data[k] = newVal;
// 通知更新
dep.notify();
},
// 做读取的劫持
get() {
// 将当前的订阅者添加到订阅器数组中
if (Dep.target) {
dep.addSub(Dep.target);
}
return vm.$data[k];
}
});
}
// 订阅器
function Dep() {
// 订阅者数组中
this.subArr = [];
}
// 将订阅者放入订阅者数组中
Dep.prototype.addSub = function (tar) {
this.subArr.push(tar)
}
// 通知每一个订阅者去更新
Dep.prototype.notify = function () {
this.subArr.forEach(sub => sub.update())
}
// 临时存储订阅者
Dep.target = null;
// 订阅者
function Watch(node, key,vm) {
Dep.target = this;
this.node = node;
this.key = key;
this.vm = vm;
this.update();
Dep.target = null;
}
Watch.prototype = {
constructor: Watch,
// 更新的方法
update() {
this.get();
if (this.node.nodeType === 1) {
this.node.value = this.value;
}
if (this.node.nodeType === 3) {
this.node.textContent = this.value;
}
},
// 获取数据的方法
get() {
this.value = this.vm[this.key];
// this.value = 实例[message]
}
}
let newVm = new Mvvm({
el: '#app', // 根元素
$data: { // 数据对象
message: 'zrr'
}
});
setTimeout(() => {
newVm.message = 'zrrrrrrrrr';
}, 3000)
// Mvvm / Mvc => 概念性
</script>
</html>