导语:
在面试中,我们经常能够被问到,vue的双向数据绑定是怎么实现的?又或者你对vue的源码深入分析,想要了解它的双向数据绑定时怎么实现的,这一篇教会你。
一,实现原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现。
就是以上的这个视图,原理就是当视图层数据发生变化的时候,就会更新data,如果data改变了,就会更新view。
详细过程是这样:
1,当data 有变化的时候它通过Object.defineProperty()方法中的set方法进行监控和更新,然后调用定义好data 和view的关系的回调函数,来通知view进行数据的改变,从而实现view视图的改变。
2,如果view 发生改变,则是通过底层的input 事件来进行data的响应更改。
二,实现过程
1,视图层改变view比较容易实现,只要通过底层的input事件就可以对data的响应更改。
2,主要难的,当data改变的时候,怎么检测到,还有怎么去更改视图层里面的数据呢?
我们先来看一个图。
data更新view的难点是如何知道数据变了,只要知道数据变了,那么接下去的事都好处理。只要通过Object.defineProperty( )对属性设置一个set函数,当数据改变了就会来触发这个函数,就可以实现data更新view了。
现在我们已经知道要实现vue的双向绑定,首先要对data进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有data。如果data发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep(容器)来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。因此接下去我们执行以下2个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有data,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新view。
流程图如下:
1,实现一个observer
function mvvm(data, key, val) {
observe(val);
Object.defineProperty(data, key, {
get: function() {
return val;
},
set: function(newVal) {
val = newVal;
}
});
}
function observe(data) {
// 如果data不是对象,或者为空就不用观察
if (!data || typeof data !== 'object') {
return;
}
// 对每一个data对象都设置观察,可以执行get和set函数
Object.keys(data).forEach(function(key) {
mvvm(data, key, data[key]);
});
};
2,实现一个dep消息订阅器和订阅者
function mvvm(data, key, val) {
observe(val);
var dep = new Dep();
Object.defineProperty(data, key,
get: function() {
if (Dep.self) {
. // 判断是否需要添加订阅者
dep.additem(Dep.self); // 在这里添加一个订阅者
}
return val;
},
set: function(newVal) {
if (val === newVal) {
return;
}
val = newVal;
dep.notify(); // 如果数据变化,通知所有订阅者
}
});
}
function Dep () {
// 这里的消息订阅是可以是一个数组
this.items = [];
}
Dep.prototype = {
addItem: function(item) {
this.items.push(item);
},
notify: function() {
this.items.forEach(function(item) {
item.update();
});
}
};
// 订阅者
function Watcher(_this, key, fun) {
this.fun = fun; //订阅者要触发的回调函数
this._this =_this; //订阅者订阅的对象
this.key = key; //订阅者订阅的key
this.value = this.get(); // 将自己添加到订阅器
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this._this.data[this.key];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.fun.call(this._this, value, oldVal);
}
},
get: function() {
Dep.self = this;
var value = this._this.data[this.key]
Dep.self = null;
return value;
}
};
3,将Observer和Watcher联系起来
function myVue (data, el, exp) {
this.data = data;
observe(data);
el.innerHTML = this.data[exp];
new Watcher(this, exp, function (value) {
el.innerHTML = value;
});
return this;
}
4,跟视图层联系
<body>
<h1 id="text">{
{text}}</h1>
</body>
<script type="text/javascript">
var ele = document.querySelector('#text');
var myVue = new myVue({
name: 'hello world'
}, ele, 'text');
</script>
补充:
微信搜索【web小馆】,回复全栈博客项目,即可获取项目源码和后续的实战文章教程。每天用最简单朴实的语言,潜移默化的提升你的计算机基础知识和前端技术。小米粥,一个专注的web全栈工程师,我们下期再见!