一、实现mvvm的双向绑定原理
1.Vue三要素
- 响应式: 例如如何监听数据变化,其中的实现方法就是我们提到的双向绑定
- 模板引擎: 如何解析模板
- 渲染: Vue如何将监听到的数据变化和解析后的HTML进行渲染
2.实现mvvm的双向绑定
是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()或者vue3.0的Proxy来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:
- 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
- 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- 实现一个订阅者Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
-
mvvm入口函数,整合以上三者
接下来分别用Proxy和defineproperty实现一个简单的数据劫持,没有指令解析器Compile和订阅者Watcher
二、Proxy和defineproperty区别
1.defineproperty
1.1.区别
- 无法监听数组变化
- 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历
- 兼容性好
1.2.实现
1.2.1.定义Vue对象
function Vue(data,el){
this.$data=data
this.el=el;
this.virtualdom=""
this.arrPro=Array.prototype;
this.arrayObj=Object.create(this.arrPro)
this.HandlerArray()
this.observe(this.$data)
}
1.2.2.利用装饰者设计模式装饰数组,实现操作数组视图更新
Vue.prototype.HandlerArray=function(){
var self=this
var arrMethods=['push','pop','shift','unshift','splice','sort','reverse'];
arrMethods.forEach((val)=>{//装饰者模式
self.arrayObj[val]=function(){
let ret=self.arrPro[val].apply(this,arguments);
self.render(arguments[0])
return ret
}
})
}
1.2.3.观察者模式,包括对数组和对象的监听。此处省略依赖收集和触发依赖
Vue.prototype.observe=function(obj){
var self=this
if(!obj) return
if(obj instanceof Array){
obj.__proto__=self.arrProObj
return
}
for(var key in obj){
var value=obj[key]
if(value instanceof Array){
value.__proto__=self.arrayObj
return
}
if(typeof value=='object'){
self.observe(value)
return
}
Object.defineProperty(obj,key,{
get:function(){
//省略依赖收集
return value
},
set:function(newVal){
//省略触发依赖
value=newVal
self.render(newVal)//代替notify
}
})
}
}
1.2.4.生成视图。此处省略读取视图模板,生成ast语法树,diff对比
//省略读取视图模板,生成ast语法树,diff对比
Vue.prototype.render=function(value){
this.virtualdom="i am is "+value
this.el.innerHTML=this.virtualdom
}
1.2.5.调用示例
var vue= new Vue({
a:'1',
b:[1,2,3]
},document.getElementById('app'))
vue.$data.a=444
setTimeout(()=>{
vue.$data.a=999
},1000)
2.Proxy
2.1.区别
- 可以直接监听对象而非属性,所以不必遍历监听属性
- Proxy可以直接监听数组的变化
- 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
- 不兼容IE,是 ES6 为了操作对象而提供的新 API
2.2.具体实现
2.2.1.定义Vue对象
function Vue(data,el){
this.$data=data
this.el=el
this.virtualdom=""
this.observe(this.$data)
}
2.2.2.观察者模式,即可以监听数据也可以监听对象。此处省略依赖收集和触发依赖。此处用到了reflect,可自行查阅http://es6.ruanyifeng.com/#docs/reflect
Vue.prototype.observe=function(obj){
this.$data=this.proxy(obj)
}
Vue.prototype.proxy=function(obj){
var self=this
var proxy=null
proxy = new Proxy(obj,{
//省略依赖收集
get:function(target,propkey){
return Reflect.get(target,propkey)
},
//省略触发依赖
set:function(target,propkey,value){
if(propkey!='length' && typeof value != 'object'){
self.render(value)//代替notify
}
return Reflect.set(target,propkey,value)
}
})
//递归监听对象
for(var index in proxy){
if(typeof proxy[index] =='object'){
proxy[index]=this.proxy(proxy[index])
}
}
return proxy
}
2.2.3.生成视图。此处省略读取视图模板,生成ast语法树,diff对比
//省略读取视图模板,生成ast语法树,diff对比
Vue.prototype.render=function(value){
this.virtualdom="i am is "+value
var pEl=document.createElement('p')
pEl.innerHTML=this.virtualdom
this.el.appendChild(pEl)
}
2.2.4.具体调用
var vue= new Vue([1,4,5],document.getElementById('app'))
vue.$data.push('444')
setTimeout(function(){
vue.$data.push('666')
},1000)
三、总结
- 从代码数量和性能上看Proxy要好很多
- 如果有兴趣朋友想了解双向数据绑定具体代码的实现,可去https://www.cnblogs.com/canfoo/p/6891868.html进行了解,个人感觉总结不错。
- 还请各位大佬指出不足之处