我的博客主页:笔头博客
Vue 3.0 马上就要发布了,3.0之前使用Object.defineProperty 这个api来实现,3.0采用新的API Proxy来实现。
记得在来现在公司面试时大神(一位资深大牛,我的朋友,我的行业领路人)问我能不能手写双向数据绑定。
转眼间已经一年半了,天资愚钝的我终于可以手撸双向数据绑定
首先我们要了解什么是双向数据绑定
简单的说 双向数据绑定就像数据库的互为主从的配置,大家数据共享,都不藏着掖着,即:视图数据改变时Model中的数据也会相应变化,当Model中的数据改变时视图也会改变,两者变化同步。
社会是现实的,编程也是如此,如果没有好处我们为什么要用双向数据绑定
好处当然很多了,首先我们不需要挨个去获取dom节点然后在去获取数据,其次数据变化时我们也不需要在去找对应的节点然后去更新,想一想这省去了程序猿多少的麻烦
下面我们开始手撸一下双向数据绑定
1、我们先了解一下双向数据绑定的整个流程
由上图可以了解到,整个双向数据绑定的过程是由:劫持监听部分、解析指令部分、更新视图,三部分组成,
劫持监听部分:是整个双向数据绑定的核心,它的作用是整理初始化时传入的数据,然后在数据更改时触发视图更新事件。
指令解析部分:遍历所有的DOM节点并按照不同的指令分配不同的监听。
更新部分:顾名思义就是更新视图
2、实现过程
2.1 基础架构
<body>
<div id="myApp">
<div v-text="message"></div>
<input v-model="message" text="input" />
</div>
<script>
class Vue {
constructor(options) {
this.el = document.querySelector(options.el);
this.data = options.data;
// 存储 监听
this.events = {};
}
// 数据劫持
observer(data) {
}
// 解析指令
compile(dom) {
}
}
// 监听器
class Watcher {
}
</script>
<script>
new Vue ({
el: '#myApp',
data: {
message: '手撸Vue'
}
})
</script>
</body>
2.2 初始化指令,将所有数据都准备一个数组来存放所有绑定该数据节点
observer(data) {
for(let key in data) {
// 初始化 指令
this.events[key] = [];
}
}
2.3 递归遍历所有节点,对绑定指令的节点放入所对应的数据数组中
compile(dom) {
// 获取节点下子节点
const childrens = dom.children;
for(let i = 0; i < childrens.length; i++) {
const dom = childrens[i];
if(dom.hasAttribute('v-text')) {
}
if(dom.hasAttribute('v-model')) {
}
// 递归 解析所有节点
if(dom.children.length > 0) {
this.compile(dom);
}
}
}
2.4 准备监听器,并初始化view
class Watcher {
constructor(dom, attr, vue, key) {
this.dom = dom;
this.attr = attr;
this.vue = vue;
this.key = key;
this.update();
}
// 更新视图
update() {
this.dom[this.attr] = this.vue.data[this.key];
}
}
2.5 绑定监听
if(dom.hasAttribute('v-text')) {
const attr = dom.getAttribute('v-text');
// 插入 监听器
this.events[attr].push(new Watcher(dom, 'textContent', this, attr));
}
if(dom.hasAttribute('v-model')) {
const attr = dom.getAttribute('v-model');
// 插入 监听器
this.events[attr].push(new Watcher(dom, 'value', this, attr));
}
到这里我们就已经能够在页面看到数据了
68.png
2.5 接下来就是监听输入框输入事件,并更新Model数据,在这里我们要使用input的input事件,因为change事件只有在失去焦点是触发,显然不是我们所需要的。
// 监听 输入框事件
dom.addEventListener('input', () => {
this.data[attr] = dom.value;
})
2.6 这是最后一步,数据劫持然后更新视图
let val = this.data[key];
// 双向数据绑定的精髓
Object.defineProperty(this.data, key, {
get: () => val,
set: newVal => {
if(newVal !== val) {
val = newVal;
// 触发 所有绑定该指令的节点更新
this.events[key].forEach(watch => {
watch.update();
})
}
}
})
完整代码
<!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>手撸Vue</title>
</head>
<body>
<div id="myApp">
<div v-text="message"></div>
<input v-model="message" text="input" />
</div>
<script>
class Vue {
constructor(options) {
this.el = document.querySelector(options.el);
this.data = options.data;
// 存储 监听
this.events = {};
this.observer(this.data);
this.compile(this.el);
}
// 数据劫持
observer(data) {
for(let key in data) {
// 初始化 指令
this.events[key] = [];
let val = this.data[key];
// 双向数据绑定的精髓
Object.defineProperty(this.data, key, {
get: () => val,
set: newVal => {
if(newVal !== val) {
val = newVal;
// 触发 所有绑定该指令的节点更新
this.events[key].forEach(watch => {
watch.update();
})
}
}
})
}
}
// 解析指令
compile(dom) {
// 获取节点下子节点
const childrens = dom.children;
for(let i = 0; i < childrens.length; i++) {
const dom = childrens[i];
if(dom.hasAttribute('v-text')) {
const attr = dom.getAttribute('v-text');
// 插入 监听器
this.events[attr].push(new Watcher(dom, 'textContent', this, attr));
}
if(dom.hasAttribute('v-model')) {
const attr = dom.getAttribute('v-model');
// 插入 监听器
this.events[attr].push(new Watcher(dom, 'value', this, attr));
// 监听 输入框事件
dom.addEventListener('input', () => {
this.data[attr] = dom.value;
})
}
// 递归 解析所有节点
if(dom.children.length > 0) {
this.compile(dom);
}
}
}
}
// 监听器
class Watcher {
constructor(dom, attr, vue, key) {
this.dom = dom;
this.attr = attr;
this.vue = vue;
this.key = key;
this.update();
}
// 更新视图
update() {
this.dom[this.attr] = this.vue.data[this.key];
}
}
</script>
<script>
new Vue ({
el: '#myApp',
data: {
message: '手撸Vue'
}
})
</script>
</body>
</html>
小结:技术日新月异,小编刚刚掌握双向数据绑定的原理,3.0就要弃用了,面对当前的局势你难道还要停滞不前么?
网友评论