主页 > 前端 > javascript >
来源:未知 时间:2020-03-15 15:22 作者:小飞侠 阅读:次
[导读] 今天带来vue双向绑定实现原理详解。 vue的双向数据绑定是区分普通对象和数组的。数组的比较复杂,下篇再介绍。今天介绍vue对于对象数据的双向绑定。vue是通过数据劫持的方式来实现...
今天带来vue双向绑定实现原理详解。 vue的双向数据绑定是区分普通对象和数组的。数组的比较复杂,下篇再介绍。今天介绍vue对于对象数据的双向绑定。vue是通过数据劫持的方式来实现双向数据绑定的。数据劫持的核心就是object.defineProperty().简单介绍下这个方法。这个方法是es5定义的,经过该方法定义的对象属性会变成访问器属性。以下是一个简单的例子: function Observer(obj,key,value){ if(Object.prototype.toString.call(value)=='[object Object]'){ Object.keys(value).forEach(function(key){ arguments.callee(value,key,value[key]); }) } Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get:function(){ }, set:function(){ } }) } 访问器属性的最大特点便是内部可以指定get、set方法。在对属性进行值访问的时候会调用定义的get方法,对属性进行赋值的时候会调用set方法。 接着说双向数据绑定。双向数据绑定分为以下三个部分: Observer:负责数据劫持,把所有的属性转换成访问器属性,达到对数据进行观测的目的。需要对数据进行递归观测,因为数据的属性值还有可能是对象 Watcher:数据的观察者,在数据发生变化之后执行的相应的回调函数,需要对数据进行递归watch,因为数据的属性值还有可能是对象 Dep(Dependency):顾名思义,是Observer和Watcher的连接。如何连接呢?每一个observer会创建一个Dep实例,实例在get数据的时候为数据收集watcher,在set的时候执行watcher内的回调方法。 以上是vue中的做法。我自己实现demo的时候就是根据这个思路进行实现的。 先是Observer,递归将属性设置为访问器属性,代码如下: function Observer(obj,key,value){ if(Object.prototype.toString.call(value)=='[object Object]'){ Object.keys(value).forEach(function(key){ new arguments.callee(value,key,value[key]); }) } Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get:function(){ return value; }, set:function(newVal){ if(value===newVal)return; value = newVal; } }) } 先判断属性值value是不是对象,如果是,还需要对对象进行递归调用,观测数据 接着是Watcher,目的在于在数据发生变化的时候执行相应的回调函数 function Watcher(data,k,v,fn){ if(Object.prototype.toString.call(data)==='[object Object]'){ Object.keys(v).forEach(function(key){ new arguments.callee(v,key,v[key],fn); }) } this.fn = fn; data[k]; } 也是先判断是不是对象,是的话递归调用观察属性值。如何让watcher和observer产生联系呢? observer中对所有的属性设置成了访问器属性,所以如果我们在watcher中调用属性,求属性的值就会调用到属性的get方法。 既然observer和watcher可以在get方法内产生连接,那么是不是可以在get的时候收集不同的watcher,然后在set函数呗调用的时候执行这些watcher中的方法。这样就需要在observer中引入一个对象,在get函数内收集watcher,在set函数内遍历执行watcher的回调方法,已达到动态响应的目的。 这个对象就是Dep。每个observer内都会实例化一个Dep对象,用于收集watcher和用于执行watcher,根据这个思路,可以得到以下的代码: function Dep(){ var sub=[]; this.addSub=function(watcher){ this.sub.push(watcher); }; this.notify=function(){ this.sub.forEach(function(watcher){ watcher.fn(); }) } } 根据思路,Dep当中需要一个存储watcher的数据结构,从添加和遍历的角度选择,数组比较合适。然后是需要一个添加watcher的方法,在就是需要一个遍历watcher的方法。进而,得出了以上的代码。 现在三个组件都已经有了,那他们之间怎么协调工作呢?按照之前的思路,和我们现在有的代码。在observer中加入Dep收集和执行依赖的代码。就发现,存在怎么在get中得到watcher的实例的问题。既然Dep本身作为watcher和observer的连接桥梁,那这个事情就让Dep做吧。此时需要做的事情是,需要一个变量,在Watcher中收集watcher实例,在get中将Watcher实例放入Dep实例的数组中,以便于set中使用。 思考这个变量的功能,他不能出现在构造函数和原型链中,这样watcher的变化会实时的提现在每个实例上。那么只有在构造函数本身这个函数对象定义这个变量比较合适了。函数对象上的变量不会通过new操作符影响到所有实例,又能完成存储watcher的功能。 先在watcher中收集,那么watcher中的代码如下: function Watcher(data,k,v,fn){ if(Object.prototype.toString.call(data)==='[object Object]'){ Object.keys(v).forEach(function(key){ new arguments.callee(v,key,v[key],fn); }) } this.fn = fn; Dep.target = this;// data[k]; Dep.target=null;// } 在watcher调用属性求值之前,将watcher保存到Dep.target变量中,在求值之后(get中Dep实例收集了之后)将该值置为null,这样就不会在别的属性求值的时候影响到别的属性。 已经在Watcher中收集到了watcher实例,那么observer中如何使用呢。看如下代码: function Observer(obj,key,value){ var dep = new Dep(); if(Object.prototype.toString.call(value)=='[object Object]'){ Object.keys(value).forEach(function(key){ new arguments.callee(value,key,value[key]); }) } Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get:function(){ if(Dep.target){//存储依赖 dep.addSub(Dep.target);// }// return value; }, set:function(newVal){ if(value===newVal)return; value = newVal; dep.notify();//执行依赖 } }) } 到这一步,我们基本上对象的双向绑定已经完成了。所有的功能都已经实现了。 在我自己运行调试的时候发现一个问题。如果set的值又是一个对象,那么对象的属性改变将无法得到监控。所以,在set中加上以上代码就完整了: if(Object.prototype.toString.call(value) ==='[object Object]'){ Object.keys(value).forEach(function(key){ new Observer(value,key,value[key]); new Watcher(value,key,value[key],function(v,key){ console.log('你修改了数据'); // document.getElementById('dd').innerHTML=v.key; }); }) } 为什么vue要分这三个部分做呢?其实watcher主要的功能也就是set的时候的回调函数。 那么如果没学习过vue的源码,应该就是直接把函数传入observer做回调函数就是了。这样watcher省了,dep也不用了。 以上就是vue双向绑定实现原理详解全部内容,感谢大家支持自学php网。 |
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习
京ICP备14009008号-1@版权所有www.zixuephp.com
网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com