前言
vue框架使用起来十分的方便,因为不用过多地去关注DOM的操作,大部分只需要关注视图层以及数据的变化,但是它是怎么实现数据同步以及双向绑定的呢,以下是我简单的概述,因为我也刚入门没有多久,所以写下这篇文章加深自己对vue的理解,文章若有错误,可以随时指正。
数据同步
首先看看vue是怎么实现数据同步的:
1.创建一个vue实例
2.给el属性设置一个接管范围,也就是vue实例在这个区域才有作用
3.设置数据
4.在html中使用插值来绑定数据
5.数据变更后,同步到html中,然后渲染到界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">{{ message }}</div>
<!-- 引用Vue库 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: "#app",// vue实例接管的区域
data: {
message: "Hello Vue!"
}
})
// 2s后数据变更
setTimeout(() => {
app.message = "bye vue!";
}, 2000);
</script>
</body>
</html>
那么按照这个思路,可以借助ES6的一个对象Proxy来模拟这个过程:
- 获取DOM,设置接管范围====>模拟el
let el = document.querySelector('#app');
- 暂存元素里面的内容,因为数据每次变更就会编译一次,那么原来的内容就不会再存在了,需要暂存
let template = el.innerHTML;
- 定义数据===>模拟data
let _data = {
name: 'laocao',
age: 22
};
- 实例化一个Proxy对象(原生JS对象)
let data = new Proxy(_data, {
set (obj, name, value) {
console.log(obj)
//alert(`外面要修改${name}为${value}`)
obj[name] = value;// 修改数据
//console.log(`数据修改了`);
// 数据变更后渲染
render();
}
});
说明:
- 这个代理对象负责监听数据变化,将真正数据隐藏,这个代理对象其实就像是一个栏杆。
- 第一个参数是“躲在栏杆后面真正的数据”,也就是proxy代理的对象。
- 第二个参数是为代理对象设置的“拦截方法”,也就是说只有通过了代理的检查,外部才能动这个真正的数据对象。
- 通过proxy代理就可以实现监听数据的变化,内部数据变化后,通过这个“栏杆”之后就可以渲染在界面上了
- proxy里面有13个方法,这是其中一个,obj其实就是“躲在栏杆背后”的数据对象,name是这个obj的属性,value是值,其实后面还有一个参数是proxy实例本身
- 只要外部请求数据,就要通过这个“栏杆”的检测,那么set就是其中一个“栏杆”。其实在vue内部不止是那么简单了,它里面会有很多的其他方法,这里为了大概说明原理,就暂时不继续深入了。
- 模拟渲染函数render
function render() {
el.innerHTML = template.replace(/\{\{\s*\w+\s*\}\}/g, str => {
str = str.substring(2, str.length - 2).trim();
return _data[str];
});
}
说明:
- 使用string原型的replace方法配合正则表达式找到html里面与数据绑定的变量,即插值表达式里面的内容,这里完全可以自己定制,插值里面可以有括号。
- replace方法第一个参数是需要替换的字符串,第二个参数是要替换的字符串或者是一个回调函数,最后replace方法返回一个新字符串
- 找到插值表达式后,先利用string原型的一个substring方法找到变量,并使用trim去除两边的括号
- 此后数据变更,就会调用这个函数,并且再将上一次div里面的模板字符串重新渲染一次,完成数据同步
注意:原字符串没有被改变!
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
姓名:{{ name }}<br/>
年龄:{{ age }}
</div>
<script>
// 1.获取DOM,设置接管范围====>模拟el
let el = document.querySelector('#app');
// 2. 暂存元素里面的内容====>因为数据每次变更就会编译一次,那么原来的内容就不会再存在了,需要暂存
let template = el.innerHTML;
// 3. 定义数据===>模拟data
let _data = {
name: 'laocao',
age: 22
};
let data = new Proxy(_data, {
set (obj, name, value) {
console.log(obj)
//alert(`外面要修改${name}为${value}`)
obj[name] = value;// 修改数据
//console.log(`数据修改了`);
// 数据变更后渲染
render();
}
});
//这个方法是数据还没变更之前,先将data里面默认的数据渲染到页面上
render();
function render() {
el.innerHTML = template.replace(/\{\{\s*\w+\s*\}\}/g, str => {
str = str.substring(2, str.length - 2).trim();
return _data[str];
});
}
</script>
</body>
</html>
双向绑定
在vue中实现双向绑定主要是应用在表单数据中,使用v-model可以实现双向数据绑定。
一个简单的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="msg" /><br>
{{ msg }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: 'hello'
}
});
</script>
</body>
</html>
1.表单输入框的值通过v-model和data的数据msg进行绑定
2.msg与与DOM中的插值表达式又是同步的,所以表单的变化会引起插值表达式的变化。
根据这个思路,对render函数的代码进行改动:
//双向绑定
Array.from(el.getElementsByTagName('input'))
.filter(element => element.getAttribute('v-model'))
.forEach(input => {
let bindData = input.getAttribute('v-model');
input.value = _data[bindData];
input.addEventListener('input', function() {
data[bindData] = this.value;
}, false)
});
- 首先需要将数据渲染到表单中,需要找到有v-model属性的标签
- 遍历这些标签,将v-model的属性值提出来,从data中找到v-model属性中绑定的值
- 另表单输入框的value等于刚刚在data中找到的值,暂时完成一方的绑定
- 监听输入框的变化,输入产生的值传入data中,完成双向数据绑定
注意:Array中的from方法是将一个伪数组转化成一个真正的数组。
总结
目前接触到只有proxy对象可以模拟,以后遇到别的方法,或者翻看vue源码的时候来继续补充这篇文章。