当前位置: 首页 > Web前端 > HTML5

(第五篇)仿《Vue生态》系列___《分析模板事件》

时间:2023-04-05 22:19:22 HTML5

(Part5)模仿‘Vue生态’系列___《解析模板事件》本任务取消‘eval’,改为‘newFunction’。支持用户使用'@'和'v-on'绑定各种事件。支持“方法”数据的初始化。使用函数时,可以传参也可以不传参,可以用'$event'。实施“c-center”和“c-show”指令。实现'cc_cb'函数,它也可以在模板中使用使用if-else。1.eval和Function项目中的值运算。之前一直用eval函数,前段时间突然发现一个很好用的函数Function。让我来证明它的魔力。1.可以执行stringletfn1=newFunction('vara=1;returna');控制台日志(fn1());//12.可以传参数下面写的name和age传入函数两个参数,letfn2=newFunction('name','age','returnname+age');console.log(fn2('lulu',24));//lulu24第二种传参方式letfn3=newFunction('name,age','returnname+age');console.log(fn3('lulu',24));//lulu24从上面可以推断,他的原理是把最后一个参数作为执行体,如果之前有参数,就会作为新生成函数的参数。3.global作用域在执行时作用域是全局的。即使是在函数内部,在执行过程中也无法获取到函数内部的值,所以我们需要手动传入我们要使用的值。//报错了,ufunctioncc(){letu=777;letfn=newFunction('vara=5;console.log(u);returna');控制台日志(fn());}cc()//成功执行functioncc(){u=777;//直接挂在窗口上letfn=newFunction('vara=5;console.log(u);returna');//777控制台日志(fn());//5}cc()我也试过了,里面的vara不会污染整个世界,放心使用;我可以用它来代替之前写的eval表达式:表达式,例如'obj[name].age'getVal(vm,expression){letresult,__whoToVar='';for(letiinvm.$data){__whoToVar+=`let${i}=vm['${i}'];`;}__whoToVar=`${__whoToVar}返回${expression}`;结果=新函数('vm',__whoToVar)(vm);返回结果;},以后会改成public'pool'来获取变量,应该在下一章做。2.所谓的'@'和'v-on'指令当然是绑定在元素上的,我们有一个compileElement方法来处理元素节点,所以就用它来让我们独立出一个命令处理模块。例如,对于命令,这次我们将执行v-show命令。事件都是原生的events.compileElement(node){letattributes=node.attributes;[...attributes].map(attr=>{letname=attr.name,value=attr.value,obj=this.isDirective(name);if(obj.type==='directive'){CompileUtil.dir[obj.attrName]&&CompileUtil.dir[obj.attrName](this.vm,node,CompileUtil.getVal(this.vm,value),value);}elseif(obj.type==='event'){//目前只处理原生事件;if(CompileUtil.eventHandler.list.includes(obj.attrName)){CompileUtil.eventHandler.handler(obj.attrName,this.vm,node,value);}else{//eventHandler[obj.attrName]该事件不是原生挂载事件,不能由处理程序处理}}});}上面有一个isDirective事件,这个事件也是一个重点。我们现在分为四种形式。判断类型,截取下面的命令名和参数,返回给handler。isDirective(attrName){if(attrName.startsWith('c-')){return{type:'command',attrName:attrName.split('c-')[1]};}elseif(attrName.startsWith(':')){return{type:'variable',attrName:attrName.split(':')[1]};}elseif(attrName.startsWith('v-on:')){return{type:'event',attrName:attrName.split('v-on:')[1]};}elseif(attrName.startsWith('@')){return{type:'event',attrName:attrName.split('@')[1]};}返回{};}cc_vue/src/CompileUtil.js这个命令处理模块专门抽出来了,暂命名为dir。本次我们以c-html和c-show为例。c-html,顾名思义,就是用户传递一段html代码,然后我注入到dom结构中structureddir:{html(vm,node,value,expr){//只有这样一个操作就足够了,没有什么深奥的东西node.innerHTML=value;//别忘了用watcher订阅这里的变化,达到双向绑定的效果。newWatcher(vm,expr,(old,newVale)=>{node.innerHTML=newVale;});}},预热后剩下的'c-center'和'c-show'控制'dom'的'display:none'属性很有意思,为'true'时显示,'dom''为'false'时将消失。该属性不能影响dom本身的行间样式,比如user定义为'none'。当它为“true”时,不能显示“dom”元素。该属性不能改变dom本身的任何属性,但优先级最高。脑海中浮现的瞬间竟然是‘!重要’。根据以上分析,得到两种解决方案:第一种:综合考虑所有外部因素,每次都进行整体分析,得出具体的结论是'block'还是'none',也可能是'flex'和'grid'等等。第二:这次我想找到一种动态插入'css'代码的新方法。这个想法很有趣。框架执行的时候,先插入一段css代码,然后利用这段css做很多很多有趣的事情,以后会扩展这方面的内容。创建一个单独的模块来插入“css”代码。单独newcc_vue/src/index.jsimportCCStylefrom'./CCStyle.js';classC{constructor(options){for(letkeyinoptions){this['$'+key]=options[key];}新的CCStyle();//...cc_vue/src/CCStyle.jsclassCCStyle{constructor(){//我想插入到最上面,js中没有插入第一个位置之类的语句,只能获取第一个元素,然后插在它的前面。让first=document.body.firstChild,style=document.createElement('style');//当然是样式标签。//这里先设置一个c-show的绝对隐藏属性。style.innerText='.cc_vue-hidden{display:noneimportant}';//放进去就会生效。以后要控制v-show,只需要为元素添加和移除这个类名即可。document.body.insertBefore(style,first);}}导出默认CCStyle;上面的代码显然不符合设计模式,我们来优化一下它的“可扩展性”。classCCStyle{constructor(){让first=document.body.firstChild,style=document.createElement('style'),typeList=this.typeList();//不管具体的属性是什么,我们直接循环出来,拼接起来即可。这里我们自己压缩。for(letkeyintypeList){style.innerText+=`.${key}{${typeList[key]}}\n`;}document.body.insertBefore(style,first);}//这里我们可以分类展开很多属性。typeList(){return{//1:'cc_vue-hidden':'display:none!important'其中控制元素被隐藏//2:'cc_vue-center':'display:flex;justify-content:center;对齐项目:中心;'};}}导出默认CCStyle;v-c输入命令cc_vue/src/CompileUtil.jscenter(vm,node,value,expr){value?node.classList.remove('cc_vue-center'):node.classList.add('cc_vue-center');newWatcher(vm,expr,(old,newVale)=>{newVale?node.classList.remove('cc_vue-center'):node.classList.add('cc_vue-center');});}c-show的原理和上面一样show(vm,node,value,expr){value?node.classList.remove('cc_vue-隐藏'):node.classList.add('cc_vue-隐藏');newWatcher(vm,expr,(old,newVale)=>{newVale?node.classList.remove('cc_vue-hidden'):node.classList.add('cc_vue-hidden');});},三。方法和事件绑定方法数据定义之后,当用户有重复定义时,友情提醒。cc_vue/src/index.jsclassC{constructor(options){//...//$methods会被在proxyVm$data.proxyVm(this.$methods,this,true)之后处理;绑定函数需要稍微改动一下,只要不传target,就绑定vm实例,noRepeat是否检测重复数据,即不报错。proxyVm(数据={},目标=this,noRepeat=false){for(letkeyindata){if(noRepeat&&target[key]){//防止data中的变量名与其他属性重复throwError(`变量名${key}重复`);}Reflect.defineProperty(target,key,{enumerable:true,//描述该属性是否会出现在forin或Object.keys()的遍历中configurable:true,//描述该属性是否配置,get是否可以被删除(){returnReflect.get(data,key);},set(newVal){if(newVal!==data[key]){Reflect.set(data,key,newVal);}}});}}方法的数据处理完之后,就要处理事件的绑定了。分配的逻辑之前已经展示过//如果事件列表中有这个事件,那么就绑定这个事件。if(CompileUtil.eventHandler.list.includes(obj.attrName)){CompileUtil.eventHandler.handler(obj.attrName,this.vm,node,value);}cc_vue/src/CompileUtil.jsmoduleeventHandler:{//这个option用于维护要处理的本机事件,以下只是示例,并不全面。list:['click','mousemove','dblClick','mousedown','mouseup','blur','focus'],//当事件确定时要执行的操作句柄er(eventName,vm,node,type){//...}}}handler要解决的问题形式是add--->直接调用.add()--->调用.add()--->与空格混合.add(n,m,9)--->与空格、常量、变量参数混合.add(n,$event)--->用户想要获取事件对象$事件。那么我们就一步步来处理这些情况吧。handler(eventName,vm,node,type){//第一步:匹配一个单词是否包含'()';if(/\(.*\)/.test(type)){//第二步:取出'()'的内容letstr=/\((.*)\)/.exec(type)[1];//删除空格str=str.replace(/\s/g,'');//用“(”拆分,得到事件名称type=type.split('(')[0];//只有'()'中有内容才进行这一步;if(str){//Step3:Parameterize'Group'letarg=str.split(',');//Step4:绑定事件和解析参数node.addEventListener(eventName,e=>{//循环这个参数组for(leti=0;i{vm[type].call(vm);//这个必须指向vm,毕竟用户使用$data和其他属性},false);}上面没有处理参数是$data上变量的情况,因为没必要,以后写c-for的时候,着重改写这里的逻辑。四。在模板中使用if我们在使用vue开发的时候,模板中只允许使用表达式。这次我玩的项目是允许用户使用任何形式来写的。当然这样在性能等方面有一定的劣势,但是为了好玩,我什么都愿意尝试,放弃返回值的写法,采用回调的方式。关键字cc_cb(value)value是要传输的值。用法如下:

{{if(n>3){cc_cb(n)}else{cc_cb('n小于等于3')};}}
其实这个功能不复杂,但是写起来很烦,而且老婆违反了设计模式。只需更改getVal函数getVal(vm,expression){letresult,__whoToVar='';for(letiinvm.$data){__whoToVar+=`let${i}=vm['${i}'];`;}//当检测到cc_cb被调用时if(/cc_cb/.test(expression)){//无非就是返回返回值__whoToVar=`let_res;函数cc_cb(v){_res=v;}${__whoToVar}${expression};return_res`;}否则{__whoToVar=`${__whoToVar}返回${expression}`;}result=newFunction('vm',__whoToVar)(vm);返回结果;},嘿嘿,只需要一个小小的改动就可以实现这么神奇的东西。接下来,我将针对价值问题进行深度优化。想想就很兴奋。下一集:优化价值。添加钩子生命周期钩子。github:链接说明个人技术博客:链接说明更多文章,ui库编写文章列表:链接说明