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

vue实现原理及简单实例实现

时间:2023-03-31 17:37:02 vue.js

创建时间:2020-09-11在线演示demo主要理解并实现了以下方法:Observe:监听器监听属性变化并通知订阅者Watch:订阅者接收属性变化并更新视图编译:Parser解析指令,初始化模板,绑定订阅者,绑定事件Dep:存放所有对应的watcher实例。主执行流程将watchers加载到对应dep实例的订阅列表的流程相关html代码中,用于解析绑定数据这里的代码是编译时要解析的模板,解析v-model{等命令{}}v-on

姓名:

学号:

学号:{{number}}

计算实现:{{getStudent}}

事件绑定

观察者代码对data数据添加get和set来添加watcher,并创建一个Dep实例通知更新viewconstdefineProp=function(obj,key,value){observe(value)/**创建一个唯一的depfor预先定义每个不同的属性*/constdep=newDep()Object.defineProperty(obj,key,{get:function(){/**根据不同的属性创建,只在创建Watcher时调用*/if(Dep.target){dep.targetAddDep()}returnvalue},set:function(newVal){if(newVal!==value){/**这里的赋值操作是在get方法中返回值,因为赋值后会立即调用get方法*/value=newVal/**通知监听属性的所有订阅者*/dep.notify()}}})}constobserve=function(obj){if(!obj||typeofobj!=='object')returnObject.keys(obj).forEach(function(key){defineProp(obj,key,obj[key])})}Dep代码主要是将watcher放入对应的dep订阅列表中letUUID=0functionDep(){this.id=UUID++//存储当前属性的所有监听watcherthis.subs=[]}dep.prototype.addSub=function(watcher){this.subs.push(watcher)}//目的是将当前dep实例传递给watcherDep.prototype.targetAddDep=function(){//这里是实例化depDep。目标。添加部门p(this)}Dep.prototype.notify=function(){//触发当前属性的所有观察者this.subs.forEach(_watcher=>{_watcher.update()})}Dep.target=nullWatcher代码数据更新,更新视图函数Watcher(vm,prop,callback){this.vm=vmthis.prop=propthis.callback=callbackthis.depId={}this.value=this.pushWatcher()}Watcher.prototype={update:function(){/*更新值变化*/constvalue=this.vm[this.prop]constoldValue=this.valueif(value!==oldValue){this.value=valuethis.callback(value)}},//目的是接收dep实例,用于将当前watcher实例放入subsaddDep:function(dep){if(!this.depId.hasOwnProperty(dep.id)){dep.addSub(这个)这个。depId[dep.id]=dep.id}else{console.log('已经存在');}},pushWatcher:function(){//存储订阅者Dep.target=this//触发对象的get监听,将上面赋值给target的this添加到subsvarvalue=this.vm[this.prop]//添加后删除Dep.target=null返回值}}编译代码解析html模板,创建代码片段,绑定数据事件functionCompile(vm){this._vm=vmthis._el=vm._elthis.methods=vm._methodsthis.fragment=nullthis.init()}Compile.prototype={init:function(){this.fragment=this.createFragment()this.compileNode(this.fragment)//代码片段中的内容编译后插入DOMthis._el.appendChild(this.fragment)},//根据真实的DOM节点创建文档片段createFragment:function(){constfragment=document.createDocumentFragment()letchild=this._el.firstChildwhile(child){//在文档片段中添加一个节点后,该节点会从原来的位置删除,相当于移动了节点位置fragment.appendChild(child)child=this._el.firstChild}returnfragment},compileNode:function(fragment){让childNodes=fragment.childNodes;[...childNodes].forEach(node=>{if(this.isElementNode(node)){this.compileElementNode(node)}letreg=/\{\{(.*)\}\}///获取节点下的所有文本内容lettext=node.textContent//判断是否是纯文本节点,文本内容是否有{{}}if(this.isTextNode(node)&®.test(text)){letprop=reg.exec(text)[1].trim()this.compileText(node,prop)}if(node.childNodes&&node.childNodes.length){//递归编译子节点this.compileNode(node)}})},compileElementNode:function(element){//获取属性,只有elements节点有如下方法letnodeAttrs=element.attributes;[...nodeAttrs].forEach(attr=>{letname=attr.nameif(this.isDirective(name)){/**v-model放在可接受的输入事件的标签上*/letprop=属性值if(name==='v-model'){/**获取到的值即需要绑定的数据*/this.compileModel(element,prop)}elseif(this.isEvent(name)){/**绑定事件*/this.bindEvent(element,name,prop)}}})},compileModel:function(element,prop){让val=this._vm[prop]this.updateElementValue(element,val)newWatcher(this._vm,prop,value=>{this.updateElementValue(element,value)})element.addEventListener('input',event=>{letnewValue=event.target.valueif(val===newValue)returnthis._vm[prop]=newValue})},compileText:function(textNode,prop){lettext=''if(/\./.test(prop)){varprops=prop.split('.')text=this._vm[props[0]][props[1]]}否则{text=this._vm[prop]}this.updateText(textNode,text)console.log(text);newWatcher(this._vm,prop,(value)=>{this.updateText(textNode,value)})},bindEvent:function(element,name,prop){vareventType=name.split(':')[1]varfn=this._vm._methods[prop]element.addEventListener(eventType,fn.bind(this._vm))},/**判断属性是否为指令*/isDirective:function(text){return/v-/.test(text)},isEvent:function(text){return/v-on/.test(text)},isElementNode:function(node){//元素节点返回1个文本节点(元素中的文本orattribute)3attributenode2(deprecated)returnnode.nodeType===1},isTextNode:function(node){returnnode.nodeType===3},updateElementValue:function(element,value){element.value=复制代码价值||''},updateText:function(textNode,value){textNode.textContent=value||''}}vue简要构造函数主要实现数据的双向绑定,自定义事件,computedfunctionFakeVue(options){this._data=options.datathis._methods=options.methodsthis._computed=options.computedthis._el=document.querySelector(options.el)//将_data中的属性代理给外层vm,这里只代理了_data的第一层属性Object.keys(this._data).forEach(key=>{this._proxyData(key)})this._initComputed()this._init()}FakeVue.prototype._init=function(){//开始递归监控对象的所有属性,直到属性值为值类型observe(this._data)newCompile(this)}FakeVue.prototype._proxyData=function(key){Object.defineProperty(this,key,{get:function(){returnthis._data[key]},set:function(value){this._data[key]=value}})}FakeVue.prototype._initComputed=function(){//简单实现:将值挂载到顶部constcomputed=this._computedObject.keys(computed).forEach((v)=>{Object.defineProperty(this,v,{得到:计算[v],设置:函数(value){}})})}创建vue实例try{letvm=newFakeVue({el:'#app',data:{name:'warren',number:'10011',score:{math:90}},computed:{getStudent:function(){return`${this.name}:studentnumberis${this.number}`}},methods:{//通过将事件绑定到元素来实现setDatacompile:function(){alert('name:'+this.name);}}});}catch(error){console.error(error)}结论从笔者理解的角度来看这是一个简单的Vue实现原理的例子,希望对正在探索的你有所帮助。在这个例子中,主要复杂的地方在于html模板的解析,以及数据的双向绑定。建议按照代码的执行顺序来理解整个过程。代码的关键点是必须的,如果你发现有什么问题,欢迎指正。最后附上vue源码地址,主要关注核心和编译器文件;欢迎交流Github