vue 3 使用了 proxy api,有些手痒,就弄一个简单的结构玩玩吧。Proxy API 见 MDN Proxy
效果图
思路
依赖收集:Mvvm 初始化时劫持数据,并设置观察者 dep。模仿 vue 结构是在 get 时往观察者 dep 推入被观察者 watcher,然后 set 时让观察者通知所有被观察者开始更新。
数据响应:这里只是简单在 compiler 里面去扫描了一遍所有带着 v-text 和 v-model 钩子的标签做了处理:定义被观察者 watcher,被观察者的触发函数写上节点 DOM 的更新。
当然我们知道 vue 的数据响应过程比这个复杂多了,有着虚拟 DOM 和复杂的 diff 算法。
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<div v-text="text1"></div>
<input type="text" v-model="text1">
<div v-text="text2"></div>
<input type="text" v-model="text2">
</div>
<script>
class Watcher {
constructor(cb) {
this.cb = cb;
}
run() {
this.cb();
}
}
class Dep {
constructor() {
this.subs = [];
this.target = null;
}
notify() {
this.subs.forEach(item => {
item.run();
})
}
}
class Mvvm {
constructor(data) {
let that = this;
this.dep = new Dep();
this.data = new Proxy(data, {
get(obj, key, prox) {
that.dep.target && that.dep.subs.push(that.dep.target);
return obj.data[key];
},
set(obj, key, value, prox) {
obj.data[key] = value;
that.dep.notify();
return true;
}
});
this.compiler();
}
compiler() {
let that = this;
let app = document.getElementById('app');
let bindTextNodes = app.querySelectorAll('[v-text]');
let bindInputNodes = app.querySelectorAll('[v-model]');
bindTextNodes.forEach(bindTextNode => {
let textModel = bindTextNode.getAttribute('v-text');
let watcher = new Watcher(function() {
bindTextNode.innerText = that.data[textModel];
});
that.dep.target = watcher;
bindTextNode.innerText = that.data[textModel];
this.dep.target = null;
})
bindInputNodes.forEach(bindInputNode => {
let inputModel = bindInputNode.getAttribute('v-model');
let watcher = new Watcher(function() {
bindInputNode.value = that.data[inputModel];
});
this.dep.target = watcher;
bindInputNode.value = that.data[inputModel];
bindInputNode.addEventListener('input', function(evt) {
that.data[inputModel] = evt.target.value;
})
that.dep.target = null;
})
}
}
new Mvvm({
data: {
text1: '123',
text2: '789'
}
})
</script>
</body>
</html>
为什么时 Proxy 而不是 Object.defineProperty ?
我们知道 Vue2.0 的反应系统是使用 Object.defineProperty 的 getter 和 setter。 但是,Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。
而为什么使用 Proxy 替代 Object.defineProperty?Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组,还可以代理动态增加的属性。节省内存,速度加倍。
为了继续支持 IE11,Vue 3 将发布一个支持旧观察者机制和新 Proxy 版本的构建