用JavaScript实现一个简单的Vue
时间:2023-03-21 19:04:29
科技观察
vue相信大家都不陌生,而且简单易用。但是大多数人并不知道它的内部原理是怎样的。今天我们一起来实现一个简单的vue。在Object.defineProperty()实现之前,我们得先看看Object.defineProperty的实现,因为Vue主要是通过数据劫持来实现的,数据的读取和更新都是通过get和set来完成的。varobj={name:'wclimb'}varage=24Object.defineProperty(obj,'age',{enumerable:true,//enumerableconfigurable:false,//不能再定义get(){returnage},set(newVal){console.log('我改了',age+'->'+newVal);age=newVal}})>obj.age>24>obj.age=25;>我改了24->25>25从你可以看到上面通过get获取数据,通过set监听数据变化,进行相应的操作。如果还是不明白,可以去看Object.defineProperty文档。流程图html代码结构
{{form}} js调用newVue({el:'#wrap',data:{form:'这是值形式为',test:'
Iaminbold',},methods:{changeValue(){console.log(this.form)this.form='值被我改变了,你生气了吗?'}}})Vue结构classVue{constructor(){}proxyData(){}observer(){}compile(){}compileText(){}}classWatcher{constructor(){}update(){}}Vue构造函数构造函数主要是数据初始化proxyData数据代理观察者劫持监听所有数据编译解析domcompileText解析dom处理纯双花括号操作Watcher更新视图操作Vue构造函数初始化classVue{constructor(options={}){this.$el=document.querySelector(options.el);letdata=this.data=options.data;//代理数据,使其可以直接访问this.xxx形式的数据。通常,需要this.data.xxxObject.keys(data).forEach((key)=>{this.proxyData(key);});this.methods=obj。methods//事件方法this.watcherTask={};//需要监听的任务列表this.observer(data);//初始化劫持并监听所有数据this.compile(this.$el);//分析上面的dom}}主要是处理传入的数据proxyData代理dataclassVue{constructor(options={}){...}proxyData(key){letthat=this;Object.defineProperty(that,key,{configurable:false,enumerable:true,get(){returnthat.data[key];},set(newVal){that.data[key]=newVal;}});}}上面主要是将数据代理到顶层,这个.xxx直接访问dataobserver劫持监听器classVue{constructor(options={}){...}proxyData(key){...}observer(data){letthat=thisObject.keys(data).forEach(key=>{letvalue=data[key]this.watcherTask[key]=[]Object.defineProperty(data,key,{configurable:false,enumerable:true,get(){returnvalue},set(newValue){if(newValue!==value){value=newValuethat.watcherTask[key].forEach(task=>{task.update()})}})})}}也使用Object.defineProperty来监控数据,在初始化需要订阅的数据,将需要订阅的数据推送到watcherTask,需要更新的时候再批量更新数据。??以下是;遍历订阅池,批量更新视图。set(newValue){if(newValue!==value){value=newValue//批量更新图that.watcherTask[key].forEach(task=>{task.update()})}}compile解析domclassVue{constructor(options={}){......}proxyData(key){......}observer(data){......}compile(el){varnodes=el.childNodes;for(leti=0;i
0){this.compile(node)}if(node.hasAttribute('v-model')&&(node.tagName==='INPUT'||node.tagName==='TEXTAREA')){node.addEventListener('input',(()=>{letattrVal=node.getAttribute('v-model')this.watcherTask[attrVal].push(newWatcher(node,this,attrVal,'value'))node.removeAttribute('v-model')return()=>{this.data[attrVal]=node.value}})())}if(node.hasAttribute('v-html')){letattrVal=node.getAttribute('v-html');this.watcherTask[attrVal].push(newWatcher(node,this,attrVal,'innerHTML'))node.removeAttribute('v-html')}this.compileText(node,'innerHTML')if(node.hasAttribute('@click')){letattrVal=node.getAttribute('@click')node.removeAttribute('@click')node.addEventListener('click',e=>{this.methods[attrVal]&&this.methods[attrVal].bind(this)()})}}}},compileText(node,type){letreg=/\{\{(.*)\}\}/g,txt=node.textContent;if(reg.test(txt)){node.textContent=txt.replace(reg,(matched,value)=>{lettpl=this.watcherTask[value]||[]tpl.push(newWatcher(node,this,value,type))returnvalue.split('.').reduce((val,key)=>{returnthis.data[key];},this.$el);})}}}这里的代码很多,我们拆解一下,会发现很简单首先,我们先遍历el元素下的所有子节点,node.nodeType===3表示当前元素是文本节点,node.nodeType===1表示当前元素是元素节点因为有些可能是纯文本的形式,比如纯双花括号是文本明文节点,然后判断元素节点是否为There也是子节点,如果有则递归调用compile方法。重头戏来了,我们拆解一下:if(node.hasAttribute('v-html')){letattrVal=node.getAttribute('v-html');this.watcherTask[attrVal].push(newWatcher(node,this,attrVal,'innerHTML'))node.removeAttribute('v-html')}上面首先判断node节点上是否有v-html指令。如果它存在,我们发布和订阅。如何发布和订阅?只需要将需要订阅的数据推送到watcherTask,然后在设置值的时候就可以批量更新,实现双向数据绑定,即下面的操作that.watcherTask[key]。forEach(task=>{task.update()})而push的值是Watcher的一个实例。首先,它会在新的时候执行一次。模板数据在模板视图上更新。***删除当前元素属性。我们在使用Vue的时候是看不到这个命令的。如果我们不删除它,它不会影响它。至于Watcher是什么,大家可以往下看。>{task.update()})之前发布订阅之后,这里的操作就完成了,意思是把当前元素如:node.innerHTML='thisisthevalueindata',node.value='this就是表单的数据'那我们为什么不直接更新呢,还需要更新什么,不是多此一举吗?事实上,你还记得更新吗?我们需要通过调用Watcher原型上的update方法来批量更新订阅池。效果在线效果地址,可以在浏览器中看一下效果,因为我太懒了,gif效果图就不放了,哈哈????完整代码完整代码已经放在github->MyVue【责任编辑:庞桂玉电话:(010)68476606】