什么是数据响应式
数据响应式既数据双向绑定,就是把model绑定到view,当我们用javascript代码更新model时,view就是自动更新;如果用户更新了view,model的数据也自动更新了,这种情况就是双向绑定;
数据响应式原理
vue实现数据响应式的原理就是利用了Object.defineProperty()这个方法重新定义了对象获取属性(get )和设置属性(set)的操作实现的。但是在vue3.0版本中采用了ES6的proxy对象来实现。
数据响应式实现
1、首先根据上图实现整体的一个架构(包括MVVM类或者VUE类,Watcher类),这里用到一个订阅发布者设计模式;
2、实现model到view,把模型里面的数据绑定到视图;
3、最后实现view到model,当视图更新时,同时更新相对应的模型;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>响应式实现</title>
<script>
//发布者
class Vue {
constructor(options) {
this.$data = options.data;
this.$el = document.querySelector(options.el);
//容器,保存订阅者信息
this._deps = {};
this.Observer(this.$data);
this.Compile(this.$el);
}
//劫持数据
Observer(data) {
for (let key in data) {
this._deps[key] = [];
var val = data[key];
let watchers = this._deps[key];
Object.defineProperty(this.$data, key, {
get: function () {
return val;
},
set: function(newVal){
if (newVal !== val) {
val = newVal;
watchers.forEach(watcher=>{
watcher.update();
});
}
}
})
}
}
//解析指令,找到指令以后进行依赖收集-----更新视图---订阅
Compile(el) {
var nodes = el.children;
for (let i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.children.length > 0) {
this.Compile(node);
}
if (node.hasAttribute('v-text')) {
var attVal = node.getAttribute('v-text');
this._deps[attVal].push(new Watcher(node, this, attVal, 'innerHTML'));
}
if (node.hasAttribute('v-model')) {
var attVal = node.getAttribute('v-model');
this._deps[attVal].push(new Watcher(node, this, attVal, 'value'));
node.addEventListener('input', () => {
this.$data[attVal] = node.value;
});
}
}
}
}
//订阅者,自己更新视图
class Watcher {
constructor(el, vm, attVal, attr) {
this.el = el;
this.vm = vm;
this.attVal = attVal;
this.attr = attr;
this.update();
}
update() {
this.el[this.attr] = this.vm.$data[this.attVal];
}
}
</script>
</head>
<body>
<div id="app">
<h1>
数据响应式
</h1>
<div>
<!-- <div v-text="myText"></div> -->
<div v-text="myBox"></div>
<!-- <input type="text" v-model="myText"> -->
<input type="text" v-model="myBox">
</div>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
// myText: '我是一个文本',
myBox: '我是一个盒子'
}
});
</script>
</body>
</html>
效果图:
proxy
由于Object.defineProperty()以下缺陷:
- 不能检测对象属性的添加或删除的;
- 不能检测到数组长度变化(通过改变length而增加的长度不能监测到);
- 不是因为defineProperty的局限性,而是出于性能考量的,不会对数组每个元素都监听;
vue 3.0会采用proxy来实现数据的劫持,proxy是Object.defineProperty()的增强版
1、Proxy可以直接监听对象而非属性;
2、 Proxy可以直接监听数组的变化;
3、Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的;
<!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>proxy模拟实现vue的双向数据绑定</title>
</head>
<body>
<div id="app">
<span v-text="myText"></span>
<input type="text" v-model="myText"/>
</div>
<script>
class Vue{
constructor(options){
this.el = document.querySelector(options.el);
this.data = options.data;
this.deps = {};
this.Observer(this.data);
this.Compiler(this.el);
}
Observer(data){
for(let key in data){
this.deps[key] = [];
}
var deps = this.deps;
var proxy = new Proxy(data,{
get:function(target,key,receiver){
return Reflect.get(target,key,receiver);
},
set:function(target,key,newVal,receiver){
var oldVal = data[key];
if(oldVal != newVal){
let watchers = deps[key];
watchers.forEach(function(watcher){
watcher.update();
});
}
return Reflect.set(target,key,newVal,receiver);
}
})
this.newData = proxy;
}
Compiler(el){
var childs = el.children;
for(let i=0;i<childs.length;i++){
var node = childs[i];
if(node.children.length>0){
this.Compiler(node);
}
if(node.hasAttribute('v-text')){
var attrVal = node.getAttribute('v-text');
var watcher = new Watcher(node,this,attrVal,'innerHTML')
this.deps[attrVal].push(watcher);
}
if(node.hasAttribute('v-model')){
var attrVal = node.getAttribute('v-model');
var watcher = new Watcher(node,this,attrVal,'value');
this.deps[attrVal].push(watcher);
node.addEventListener('input', () => {
this.newData[attrVal] = node.value;
});
}
}
}
}
//订阅者,自己更新视图
class Watcher {
constructor(el, vm, attVal, attr) {
this.el = el;
this.vm = vm;
this.attVal = attVal;
this.attr = attr;
this.update();
}
update() {
this.el[this.attr] = this.vm.data[this.attVal];
}
}
new Vue({
el:'#app',
data:{
myText:'这个是输入框显示的内容'
}
})
</script>
</body>
</html>