用了Vue长达2年,如果自己去实现一个双向绑定,我可能一个字母都写不出来,是时候探究一下了。
先看data里某对象的输出
data() {
return {
pagination: {
layout: 'prev,pager,next,jumper,sizes,->, total',
pageSizes: [10, 20, 30, 40],
currentPage: 1,
pageSize: 10,
total: 0
}
}
},
我们可以看到对象属性都有有两个相对应的get和set方法,为什么会多出这两个方法呢?因为vue是通过Object.defineProperty()来实现数据劫持的。
问题来了,什么是Object.defineProperty()
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。注意:此方法是ES6新增的属性,而IE8不支持ES6语法 所以vue2.0不支持IE8浏览器。
关于es6新增的Object.defineProperty()方法是如何使用,可见
ES6-强大的对象方法 Object.defineProperty() - 简书
以下是本人根据别人的实例敲出来的实例子,自己动下手,加深下印象。
<script>
let obj = {
id: 1,
pname: '小米',
price: 1222
}
function displayDate(){
Object.defineProperty(obj, 'price', {
value: 1999
})
console.log(obj);
Object.defineProperty(obj, 'count', {
value: 100
})
console.log(obj)
Object.defineProperty(obj, 'id', {
// writable修改后值是否可以被重写 默认是false
writable: false
})
obj.id = 3 //重写无效
console.log(obj);
Object.defineProperty(obj, 'address', {
value: '山东',
// enumerable参数,设置是否可以被遍历 默认是false,通过该方法添加的默认都是false不会被遍历
enumerable: false,
// configurable参数,默认为false。不允许删除或修改
configurable: true //值为false时,即为不可删除或修改
})
console.log(obj)
console.log(Object.keys(obj)); //取对象属性的方法,即遍历,因为address设置了不可被遍历
delete obj.address
console.log(obj);
}
</script>
结果如下
自己动手,印象果然一下就深刻了。
再来一个示例
<script>
let obj = {
id: 1,
pname: '小米',
price: 1222
}
function displayDate(){
Object.defineProperty(obj,'pname',{
get:function(){
//当读取obj的pname的属性值的时候,会触发这个函数
console.log('get方法被调用了');
//return 的值,就是属性值
return pname;
},
set:function(val){
//set可以接收一个参数,就是你想赋的值
//当设置obj的pname的属性值的时候,会触发这个函数
console.log('set方法被调用了');
//可以通过下面方法赋值,text变量就是obj的text的属性值
pname = val;
}
})
obj.pname = '华为';
console.log(obj.pname)
}
</script>
写到这里,聪明的你应该是知道如何实现VUE的双向绑定了,是的,它就是利用了2015年ES6标准的Object.defineProperty()方法。
手动实现一个VUE双向绑定,思路:用Object.defineProperty()的set值向input填充,用input的oninput事件触发set事件。代码如下。
var model = {};
Object.defineProperty(model,'txt',{
get:function(){},
set:function(val){
var span = document.getElementsByTagName('span')[0];
span.innerHTML = val;
}
})
var input = document.getElementsByTagName('input')[0];
input.oninput = function(){
model.txt = input.value;//必然触发set函数
}
至此,简易版的VUE双向绑定如何实现的大概思路就出来了,但我们的尤雨溪大神,肯定不会这样写。一个流行框架的诞生,所需技术与思想,不是我们常人所能达到的。
双向绑定的完整实现思路是:
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
图解
1.实现一个Observer
Observer是一个数据监听器,其实现核心方法就是前文所说的Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理。如下代码,实现了一个Observer。
<script>
function defineReactive(data, key, val) {
observe(val); // 递归遍历所有子属性
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
return val;
},
set: function(newVal) {
val = newVal;
console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
}
});
}
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
var library = {
book1: {
name: ''
},
book2: ''
};
observe(library);
library.book1.name = 'vue权威指南'; // 属性name已经被监听了,现在值为:“vue权威指南”
library.book2 = '没有此书籍';
</script>
后继observer的改造,订阅者,解析器的实现本人就不研究了,略过就可以了,如有兴趣传送门。
总结:
在new Vue的时候,在 Observer 中通过 Object.defineProperty() 达到数据劫持,代理所有数据的 getter 和 setter 属性,在每次触发 setter 的时候,都会通过 Dep 来通知Watcher,Watcher 作为Observer数据监听器与Compile模板解析器之间的桥梁,当 Observer 监听到数据发生改变的时候,通过 Updater 来通知 Compile 更新视图,而 Compile 通过 Watcher 订阅对应数据,绑定更新函数,通过 Dep 来添加订阅者,达到双向绑定。
vue的双向绑定原理及实现_深海里搁浅的猫的博客-CSDN博客_vue实现双向数据绑定原理
Vue 是如何实现数据双向绑定的?_吖旭玲的博客-CSDN博客_vue如何实现数据双向绑
vue2核心对象defineProperty_明哥前端的博客-CSDN博客