Vue最独特的功能之一是其非侵入式响应系统。比如我们修改了数据,那么依赖这些数据的视图就会更新,这样就大大提高了我们“搬砖”的效率。回想一下我们刚学JS的时候,海量的Dom操作~.~......,Vue通过数据驱动视图极大的将我们从繁琐的DOM操作中解脱出来。如下图所示,我们改变了msg的值,视图响应式更新了。Vue响应式原理。我们先看vue官网上的图。其实不是很清楚。刚看到的时候一头雾水~.~:再看下图,响应式的原理都在里面了(图片来源于网络):梳理一下流程:vue初始化=>劫持数据集get,set(拦截数据读写)编译解析模板=>生成watcher=>读取数据,触发get方法=>Dep收集依赖(watcher)数据变化=>触发set方法=>通知Dep中所有watcher=>查看updateForObserver、Dep和Watcher这三个王者,我初学的时候也搞过,搞混了很傻。我的理解是:Dep(dependence)是依赖收集器,集合Watcher是观察者。Watcher是观察者,观察数据,当数据发生变化时更新对应的视图(dom)。Observer是劫持者,通过Object.defineProperty()来设置数据的get和set方法:观察msg数据将放入msg数据的依赖收集器Dep中。data(){return{msg:'hellovue',}},
{{msg}}
{{msg}}
set:当msg数据发生变化时,遍历Dep依赖收集器并通知所有Watcher更新视图,即更新h1和h2标签中的文本内容,实现Vue的响应式系统通过以上分析可知,每条数据都有一个依赖的收集器Dep,其中存储数据使用了Watcher,如下图(图片来源于网络):1.Dep我们先实现Dep,Dep可以用一个数组来模拟。它应该有两个方法:add,collectWatchernotify,数据变化时通知Watcher更新视图#DependencycollectorclassDep{constructor(){this.subs=[];}addSub(watcher){#添加观察者this.subs.push(watcher);}notify(){#通知每个观察者更新视图this.subs.forEach(watcher=>watcher.update());}}2。WatcherWatcher实现如下,其中cb是更新视图的方法,关键点是oldVal,它有两个用途:当Dep触发update方法时,比较新旧值,只有有变化才更新,以避免不必要的视图更新。初始化的时候会获取旧值,会触发数据的get方法。这时候可以在Dep中注入依赖(也就是依赖集合)#Observer,用于更新视图classWatcher{constructor(vm,expr,cb){this.vm=vm;这个.expr=expr;#查看更新函数this.cb=cb;#旧值this.oldVal=this.getOldVal();}getOldVal(){#传递手表本身Dep.target=this;#获取值时会触发get方法,将自己push到deps[]constoldVal=compileUtils.getVal(this.expr,this.vm);Dep.target=null;返回旧值;}update(){#获取新值constnewVal=compileUtils.getVal(this.expr,this.vm);如果(newVal!==this.oldVal){this.cb(newVal);Dep.target=this的用处相当于设置了一个全局变量,让Dep自己收集watcher,后面的Dep.target=null的用处是销毁全局变量3.ObserverObserver实现如下,拦截数据读写操作通过Object.defineProperty:get收集依赖,注意判断Dep.target是否有值,因为解析模板的时候也会读取数据触发get方法集合通知依赖Collector,updateview//数据劫持类Observer{constructor(data){this.observer(data,key,data[key]);}observer(obj,key,value){constdep=newDep();Object.defineProperty(obj,key,{enumerable:true,configurable:false,get(){#防止视图在初始化时被收集到Dep中Dep.target&&dep.addSub(Dep.target);returnvalue;},set:newVal=>{this.observer(newVal);if(newVal!==value){value=newVal;#通知依赖收集器,有变化dep.notify();}},});}}4。编译至此我们已经实现了Observer、Dep和Watcher,实现了数据的响应式跟踪,但是还有一点没有搞清楚,那就是依赖收集,那么什么时候收集依赖呢?换句话说,我们如何知道哪些数据取决于哪些视图?Vue在解析模板的时候,其实我们已经知道哪个Dom依赖了哪些数据,所以模板的解析和依赖收集都是在编译的时候完成的编译实现如下,省略了大部分dom操作相关的代码,可以使用DocumentFragment文档片段来提升性能,逻辑比较简单,我们在dom解析数据的时候生成对应的watcher,完成依赖收集:#编译类,输出真实的DomclassCompile{constructor(el,vm){this.el=this.isElementNode(el)?el:文档.querySelector(el);这个.vm=虚拟机;#获取文档对象constfragment=this.nodeFragment(this.el);#编译this.compile(fragment);#挂载回appthis.el.appendChild(fragment);}#元素节点是否为ElementNode(node){returnnode.nodeType===1;}#获取文档片段nodeFragment(el){#dosomething}compile(fragment){constchildNodes=fragment.childNodes;[...childNodes].forEach(node=>{if(this.isElementNode(node)){#elementnode#dosomething}else{#Textnode#dosomething}})}}#执行不同的编译操作根据不同的指令constcompileUtils={#v-texttext(node,expr,vm){constvalue=vm.$data[expr];#创建观察者完成依赖收集newWatcher(vm,expr,newVal=>{node.textContent=value;});不de.textContent=值;},};至此,一个响应式系统就结束了。什么是双向数据绑定?上面我们实现了一个响应式系统,但是它只是单向的,也就是数据驱动视图,什么是双向数据绑定呢?如下图所示:我们常见的v-model是双向数据绑定。其实就是一个语法糖:
相当于=>
实现两个-方式数据绑定,即:数据变化=>视图更新视图变化=>数据变化=>视图更新比如最简单的输入,我们只需要监听输入事件,文本发生变化时更新数据,触发数据的set方法,通知所有观察者更新视图。在编译模板的时候,我们给dom元素绑定相应的事件,比如给input标签绑定input事件,指定更新数据的回调函数:constcompileUtils={#v-modelmodel(node,expr,vm){常量值=vm.$data[expr];#创建观察者完成依赖收集newWatcher(vm,expr,newVal=>{node.value=value;});node.addEventListener('input',(e)=>{#更新数据,触发数据集方法vm.$data[expr]=newVal;});节点值=值;},};在这里你完成了源代码源代码END