完善了一下compile部分,还是有问题。在此简要记录下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text">
<p>{{text}} {{test}}</p>
<button id="btn">click</button>
</div>
<script>
class Observe {
constructor(data) {
this.data = data;
this.walk();
}
walk() {
let keys = Object.keys(this.data);
for (let key of keys) {
this.defineReactive(this.data, key, this.data[key]);
}
}
defineReactive(data, key, value) {
let dep = new Dep();
let that = this;
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.addWatcher(Dep.target); // 在消息订阅器中注册
}
return value;
},
set(newVal) {
if (newVal === value) return;
// console.log(2, newVal);
value = newVal;
data[key] = newVal;
that.observer(newVal);
dep.notify();
return newVal;
}
})
}
observer(obj) {
if (obj && typeof obj === 'object') new Observe(obj);
}
}
class Dep {
constructor() {
this.watchers = [];
}
addWatcher(watcher) {
this.watchers.push(watcher);
}
notify() {
this.watchers.forEach(v => {
v.update();
})
}
}
class Watcher {
constructor(exp, cb, vm) {
this.cb = cb;//callback
this.vm = vm;//MyVue
this.exp = exp;//对应的key
this.value = this.get(); // value存储旧值
}
update() {
this.run();
}
run() {
let value = this.vm.data[this.exp];
let oldVal = this.value;
this.cb.call(this.vm, value, oldVal);
}
get() {
Dep.target = this;
let value = this.vm.data[this.exp];
Dep.target = null;
return value;
}
}
class Compile {
constructor(vm) {
this.vm = vm;
new Observe(vm.data)
}
run() {
let that = this;
let node = this.vm.$el;
let inp = document.querySelectorAll(`#${node.id} input[v-model]`);
[...inp].forEach(ele => {
let exp = ele.getAttribute('v-model');
ele.value = that.vm.data[exp]; // 初始化
new Watcher(exp, function (value, oldVal) {
// this 为 vm 对象
ele.value = value;
}, that.vm);
ele.addEventListener('input', function (e) {
that.vm.data[exp] = e.target.value;
/* 触发input事件。赋值时触发set
* set 触发 dep 的 notify
* 触发 watcher 的 run
* 调用cb函数, 修改view
*/
})
})
// 此处是获取children去解析{{}},事实上应该修改的是text节点
let child = node.children;
child = [...child].filter(v => v.innerText.trim().includes("{{"));
// console.log(child)
child.forEach(ele => {
let txt = ele.innerText; // 记录原始字符串
let a = ele.innerText.indexOf("{{"), b = ele.innerText.indexOf('}}');
while (a != -1 && b != -1) {
let exp = ele.innerText.slice(a + 2, b).trim(); // 获得key
new Watcher(exp, function (value, oldVal) {
// console.log(exp, value);
// 重新对该部分的{{}}进行解析 (有问题)
ele.innerText = txt;
let a = txt.indexOf("{{"), b = txt.indexOf('}}');
while (a != -1 && b != -1) {
let exp = ele.innerText.slice(a + 2, b).trim(); // 获得key
ele.innerText = ele.innerText.replace(`{{${exp}}}`, that.vm.data[exp]);
a = ele.innerText.indexOf("{{"), b = ele.innerText.indexOf('}}');
}
ele.innerText = ele.innerText.replace(`{{${exp}}}`, value);
// console.log('ptext', value);
}, that.vm);
ele.innerText = ele.innerText.replace(`{{${exp}}}`, that.vm.data[exp]);
a = ele.innerText.indexOf("{{"), b = ele.innerText.indexOf('}}');
}
})
}
}
class MyVue {
constructor({ el, data }) {
this.$el = document.querySelector(el);
this.data = data;
this.comp = new Compile(this);
this.comp.run()
}
}
let app = new MyVue({
el: '#app',
data: {
text: 'hello',
test: 'testString'
}
})
document.querySelector('#btn').addEventListener('click', () => { app.data.text = '999' })
/* v-on解析没写,此处只是用button测试修改text能否同步修改input的value */
</script>
</body>
</html>
是根据自己对于发布-订阅模式写的,有挺多有问题的地方的。
比如使用childnodes是无法获得直接以textnode结点写入的{{value}}的。
对于input部分的逻辑有了更深的理解。
对于input输入框和v-model,事实上是绑定了input事件。
当触发input事件时:
(1)赋值,触发被劫持的set函数。
(2)set函数中告知dep该值有变化
(3)dep通知watcher即订阅者列表。
(4)watcher执行update函数,通过cb函数实现对view的修改。
而并非触发input事件时执行后三步。