当前位置: 首页 > Web前端 > vue.js

Vue响应式系统原理及实现双向绑定_1

时间:2023-04-01 01:34:11 vue.js

本章主要关注两点:响应式系统如何收集依赖和响应式系统如何更新视图我们知道数据劫持是通过Object.defineProperty来完成的,当数据发生变化时,get方法收集依赖,然后set方法调用dep.notify方法通知Watcher调用自己的update方法更新视图。那我们先抛开其他问题,讨论get、notify、update等方法,直接上传代码:get()get:functionreactiveGetter(){constvalue=getter?getter.call(obj):valif(Dep.target){dep.depend()if(childOb){childOb.dep.depend()if(Array.isArray(value)){dependArray(value)}}}返回value}我们知道Dep.target在创建Watcher时是null,它只是作为一个标记。当我们创建一个Watcher实例时,我们的Dep.target会被赋值给Watcher实例,然后放入target栈中。这里我们调用pushTarget函数://将watcher实例赋值给Dep.target进行依赖收集。同时将实例存入目标栈导出函数pushTarget(_target:?Watcher){if(Dep.target)targetStack.push(Dep.target)Dep.target=_target}然后我们继续执行if(Dep.target)Dep.depend函数会在声明时被调用://将自己添加到全局观察者depend(){if(Dep.target){Dep.target.addDep(this)}}那么什么是下面的childOb??letchildOb=!shallow&&observe(val)我们通过这个变量来判断当前属性下是否有ob属性。如果有,继续调用Dep.depend函数,如果没有,我们就不处理了。我们还需要处理当前传入的值类型,如果是数组属性,我们会调用dependArray收集数组依赖//收集数组依赖函数dependArray(value:Array){for(lete,i=0,l=value.length;iindex&&queue[i].id>watcher.id){i--}queue.splice(i+1,0,watcher)}//刷新队列if(!waiting){waiting=truenextTick(flushSchedulerQueue)}}}我们可以看到一个更新队列指向:functioncallUpdatedHooks(queue){leti=queue.lengthwhile(i--){constwatcher=queue[i]constvm=watcher.vmif(vm._watcher===watcher&&vm._isMounted){callHook(vm,'updated')}}}我们的回调调用更新的钩子。这里有点超一流。我们在初始化rendering的时候会调用一个initRender函数来创建dom,上面提到的nextTick后面会讲到,所以了解了更新机制后,下一章我们会实现一个让面试官目瞪口呆的双向绑定。我们对Vue的响应式系统有了一定的了解,并且知道它是如何实现数据更新视图,视图改变数据的,那么就有这样的基础了,我手写一个MVVM,这样在面试的时候,就可以吊死面试官了(开个玩笑,不够用,呵呵)然后先抛出一张大家已经不熟悉的图:1.当我们有两个newMVVM经过Step-by-step操作,Observer,Compile,我们知道Observer是做数据劫持,Compile是解析指令,那么问题来了:Observer为什么要进行数据劫持?为什么Compile需要解析指令?带着这两个问题,我们来回顾一下之前的内容:什么是数据响应?数据响应性的原理是什么?数据响应是如何实现的?数据响应是双向数据绑定,即将Model绑定到View。当我们用JavaScript代码更新Model时,View会自动更新;如果用户更新View,Model数据也会自动更新。在这种情况下它是双向绑定。数据响应式原理Vue通过Object.defineProperty()方法实现数据响应式原理,重新定义了对象获取属性值get和set属性值set的操作。在Vue3.0中,是通过ECMAScript6中的代理对象proxy来实现的。的。那么这一章就是实现数据响应。那么回答前面两个问题,为什么要劫持数据?为什么要解析指令?只有当数据被劫持时,才能对数据进行监控,以便及时更新数据,更新视图。Vue中自定义了N多条指令。只有解析它,我们的JavaScript才能识别它并运行它。此类问题我们不再赘述,下面开始实现数据响应。在写demo之前,我们应该梳理一下自己的思路:1.首先实现整体架构(包括MVVM类或者VUE类,Watcher类),/这里我们采用订阅-发布者的设计模式。2、然后在MVVM中实现从M到V,将模型中的数据绑定到视图中。3、最后实现V-M。当在文本框中输入文本时,模型中的数据由文本事件触发。4、同时对应的视图也更新了。//html代码

MVVM双向绑定

我们创建了两个div实现输入框和input的数据关联,说白了就是同一个数据源,那么我们的数据源在哪里呢?//数据源dataconstapp=newVue({el:"#app",data:{myText:"祝你好运!今晚吃鸡!",myBox:"我是一个盒子!",},});可见我们需要一个Vue类,也就是发布者,所以直接上代码://Vue类(发布者)classVue{}有一个发布者,我们还需要一个订阅者://Watcher类(订阅者)classWatcher{}可以看到两者都可用,那么如何实现呢?获取数据获取元素对象构造一个对象来存储订阅者classVue{constructor(optios){this.$data=optios.data;//获取数据this.$el=document.querySelector(optios.el);//获取元素对象this._directive={};//Storesubscribers}}然后我们说,我们需要劫持数据,解析指令,然后我们必须构造两个方法classVue{constructor(optios){this.$data=optios.data;//获取数据this.$el=document.querySelector(optios.el);//获取元素对象this._directive={};//存储订阅者this.Observer(this.$data);这个。编译(这个。$el);}//劫持数据Observer(data){Object.defineProperty(this.$data,key,{get:function(){},set:function(){}},});}//解析指令//view--->object-->instructionCompile(el){}}一个是劫持Data,一个是解析元素命令,被劫持的属性要根据属性分配一个容器,当该属性在当前容器中不存在,我们需要将其添加到订阅者对象中,等待通知更新。for(letkeyindata){this._directive[key]=[];让val=数据[键];让watch=this._directive[key];}然后解析命令,首先要递归当前节点,是否有子节点,是否有v-text指令,v-model指令。让节点=el.children;for(leti=0;i{//赋值给模型this.$data[attrVal]=node.value;//console.log(this.$data);});然后我们需要在触发更新时收集依赖,我们直接返回收集到的依赖Object.defineProperty(this.$data,key,{get:function(){returnval;}}那么我们的订阅者长什么样呢?我们的订阅者接收当前元素信息、MVVM对象、标识符和属性并且需要构造一个update方法updateclassWatcher{constructor(el,vm,exp,attr){this.el=el;这个.vm=虚拟机;这个.exp=exp;this.attr=属性;这个更新();}//更新视图update(){this.el[this.exp]=this.vm.$data[this.attr];//div.innerHTML/value=this.Vue.$data["myText/myBox"]}}到这里就差不多结束了,所以我们要收集依赖,通知watcher更新view,于是来了:Object.defineProperty(this.$data,key,{get:function(){returnval;},set:function(newVal){if(newVal!==val){val=newVal;watch.forEach(element=>{元素。更新();});}},});这样做,就可以实现数据响应式。掌握了响应式原理,于是开始着手Vue的另一个核心概念组件系统