为了账号安全,请及时绑定邮箱和手机立即绑定

一套代码小程序&Web&Native运行的探索02

标签:
前端工具

接上文:一套代码小程序&Web&Native运行的探索01,本文都是一些探索性为目的的研究学习,在最终版输出前,内中的内容可能会有点乱

参考:

https://github.com/fastCreator/MVVM

https://www.tangshuang.net/3756.html

https://www.cnblogs.com/kidney/p/8018226.html

经过之前的学习,发现Vue其实与小程序框架相识度比较高,业内也有mpvue这种还比较成熟的方案了,我们这边依旧不着急去研究成熟的框架,现在看看自己能做到什么程度,最近也真正的开始接触了一些Vue的东西,里面的代码真的非常不错,研究学习了下Vue的结构,发现其实跟我们要的很类似,这里想要尝试初步的方案:提供Html模板->解析Html模板,其实这里就是Vue里面Parse部分的逻辑,一小部分代码,这样有很多Vue的代码可以借鉴,也变相的学习Vue的源码,一举两得,于是我们速度开始今天的学习

首先,我们设置一个简单的目标:设置一段简单的小程序模板,当我们做完web版本后,他可以在小程序中运行

<view class="c-row search-line" data-flag="start" ontap="clickHandler">
  <view class="c-span9 js-start search-line-txt">
    {{name}}</view></view>

复制代码

 1 Page({ 2   data: { 3     name: 'hello world' 4   }, 5   clickHandler: function () { 6     this.setData({ 7       name: '叶小钗' 8     }) 9   }10 })

复制代码

这里第一个关键便是将html模板转换为js代码,如果是之前我们直接会用这种代码:

复制代码

 1 _.template = function (text, data, settings) { 2   var render; 3   settings = _.defaults({}, settings, _.templateSettings); 4  5   // Combine delimiters into one regular expression via alternation. 6   var matcher = new RegExp([ 7     (settings.escape || noMatch).source, 8     (settings.interpolate || noMatch).source, 9     (settings.evaluate || noMatch).source10   ].join('|') + '|$', 'g');11 12   // Compile the template source, escaping string literals appropriately.13   var index = 0;14   var source = "__p+='";15   text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {16     source += text.slice(index, offset)17       .replace(escaper, function (match) { return '\\' + escapes[match]; });18 19     if (escape) {20       source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";21     }22     if (interpolate) {23       source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";24     }25     if (evaluate) {26       source += "';\n" + evaluate + "\n__p+='";27     }28     index = offset + match.length;29     return match;30   });31   source += "';\n";32 33   // If a variable is not specified, place data values in local scope.34   if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';35 36   source = "var __t,__p='',__j=Array.prototype.join," +37     "print=function(){__p+=__j.call(arguments,'');};\n" +38     source + "return __p;\n";39 40   try {41     render = new Function(settings.variable || 'obj', '_', source);42   } catch (e) {43     e.source = source;44     throw e;45   }46 47   if (data) return render(data, _);48   var template = function (data) {49     return render.call(this, data, _);50   };51 52   // Provide the compiled function source as a convenience for precompilation.53   template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';54 55   return template;56 };

复制代码

underscore里面的代码

将上述代码做字符串处理成字符串函数,然后将data传入,重新渲染即可。然而技术在变化,在进步。试想我们一个页面某个子节点文字发生了变化,全部重新渲染似乎不太划算,于是出现了虚拟DOM概念(React 导致其流行),他出现的意义就是之前我们使用jQuery操作10次dom的时候浏览器会操作10次,这里render过程中导致的坐标计算10次render tree的形成可能让页面变得越来越卡,而虚拟DOM能很好的解决这一切,所以这里我们就需要将我们模板中的代码首先转换为虚拟DOM,这里涉及到了复杂的解析过程

PS:回到最初Server渲染时代,每次点击就会导致一次服务器交互,并且重新渲染页面

Virtual DOM

我们做的第一步就是将模板html字符串转换为js对象,这个代码都不要说去实现,光是想想就知道里面必定会有大量的正则,大量的细节要处理,但我们的目标是一套代码多端运行,完全没(能力)必要在这种地方耗费时间,所以我们直接阅读这段代码:https://johnresig.com/blog/pure-javascript-html-parser/,稍作更改后,便可以得到以下代码:

复制代码

  1 /*  2  * Modified at https://github.com/blowsie/Pure-JavaScript-HTML5-Parser  3  */  4   5 // Regular Expressions for parsing tags and attributes  6 let startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:@][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,  7     endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/,  8     attr = /([a-zA-Z_:@][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g  9  10 // Empty Elements - HTML 5 11 let empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr") 12  13 // Block Elements - HTML 5 14 let block = makeMap("a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video") 15  16 // Inline Elements - HTML 5 17 let inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var") 18  19 // Elements that you can, intentionally, leave open 20 // (and which close themselves) 21 let closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr") 22  23 // Attributes that have their values filled in disabled="disabled" 24 let fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected") 25  26 // Special Elements (can contain anything) 27 let special = makeMap("script,style") 28  29 function makeMap(str) { 30     var obj = {}, items = str.split(","); 31     for (var i = 0; i < items.length; i++) 32         obj[items[i]] = true; 33     return obj; 34 } 35  36 export default function HTMLParser(html, handler) { 37     var index, chars, match, stack = [], last = html; 38     stack.last = function () { 39         return this[this.length - 1]; 40     }; 41  42     while (html) { 43         chars = true; 44  45         // Make sure we're not in a script or style element 46         if (!stack.last() || !special[stack.last()]) { 47  48             // Comment 49             if (html.indexOf("<!--") == 0) { 50                 index = html.indexOf("-->"); 51  52                 if (index >= 0) { 53                     if (handler.comment) 54                         handler.comment(html.substring(4, index)); 55                     html = html.substring(index + 3); 56                     chars = false; 57                 } 58  59                 // end tag 60             } else if (html.indexOf("</") == 0) { 61                 match = html.match(endTag); 62  63                 if (match) { 64                     html = html.substring(match[0].length); 65                     match[0].replace(endTag, parseEndTag); 66                     chars = false; 67                 } 68  69                 // start tag 70             } else if (html.indexOf("<") == 0) { 71                 match = html.match(startTag); 72  73                 if (match) { 74                     html = html.substring(match[0].length); 75                     match[0].replace(startTag, parseStartTag); 76                     chars = false; 77                 } 78             } 79  80             if (chars) { 81                 index = html.indexOf("<"); 82  83                 var text = index < 0 ? html : html.substring(0, index); 84                 html = index < 0 ? "" : html.substring(index); 85  86                 if (handler.chars) 87                     handler.chars(text); 88             } 89  90         } else { 91             html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function (all, text) { 92                 text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, "$1$2"); 93                 if (handler.chars) 94                     handler.chars(text); 95  96                 return ""; 97             }); 98  99             parseEndTag("", stack.last());100         }101 102         if (html == last)103             throw "Parse Error: " + html;104         last = html;105     }106 107     // Clean up any remaining tags108     parseEndTag();109 110     function parseStartTag(tag, tagName, rest, unary) {111         tagName = tagName.toLowerCase();112 113         if (block[tagName]) {114             while (stack.last() && inline[stack.last()]) {115                 parseEndTag("", stack.last());116             }117         }118 119         if (closeSelf[tagName] && stack.last() == tagName) {120             parseEndTag("", tagName);121         }122 123         unary = empty[tagName] || !!unary;124 125         if (!unary)126             stack.push(tagName);127 128         if (handler.start) {129             var attrs = [];130 131             rest.replace(attr, function (match, name) {132                 var value = arguments[2] ? arguments[2] :133                     arguments[3] ? arguments[3] :134                         arguments[4] ? arguments[4] :135                             fillAttrs[name] ? name : "";136 137                 attrs.push({138                     name: name,139                     value: value,140                     escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"141                 });142             });143 144             if (handler.start)145                 handler.start(tagName, attrs, unary);146         }147     }148 149     function parseEndTag(tag, tagName) {150         // If no tag name is provided, clean shop151         if (!tagName)152             var pos = 0;153 154         // Find the closest opened tag of the same type155         else156             for (var pos = stack.length - 1; pos >= 0; pos--)157                 if (stack[pos] == tagName)158                     break;159 160         if (pos >= 0) {161             // Close all the open elements, up the stack162             for (var i = stack.length - 1; i >= pos; i--)163                 if (handler.end)164                     handler.end(stack[i]);165 166             // Remove the open elements from the stack167             stack.length = pos;168         }169     }170 };

复制代码

View Code

这是一段非常牛逼的代码,要写出这种代码需要花很多功夫,绕过很多细节,自己写很难还未必写得好,所以拿来用就好,不必愧疚......,但是我们需要知道这段代码干了什么:

他会遍历我们的字符串模板,解析后会有四个回调可供使用:start、end、chars、comment,我们要做的就是填充里面的事件,完成我们将HTML转换为js对象的工作:

复制代码

 1 <!doctype html> 2 <html> 3 <head> 4   <title>起步</title> 5 </head> 6 <body> 7  8 <script type="module"> 9 10   import HTMLParser from './src/core/parser/html-parser.js'11 12   let html = `13 <div class="c-row search-line" data-flag="start" ontap="clickHandler">14   <div class="c-span9 js-start search-line-txt">15     {{name}}</div>16 </div>17   `18 19   function arrToObj(arr) {20     let map = {};21     for(let i = 0, l = arr.length; i <  l; i++) {22       map[arr[i].name] = arr[i].value23     }24     return map;25   }26 27   //存储所有节点28   let nodes = [];29 30   //记录当前节点位置,方便定位parent节点31   let stack = [];32 33   HTMLParser(html, {34     /*35      unary: 是不是自闭和标签比如 <br/> input36      attrs为属性的数组37     */38     start: function( tag, attrs, unary ) { //标签开始39       /*40        stack记录的父节点,如果节点长度大于1,一定具有父节点41        */42       let parent = stack.length ? stack[stack.length - 1] : null;43 44       //最终形成的node对象45       let node = {46         //1标签, 2需要解析的表达式, 3 纯文本47         type: 1,48         tag: tag,49         attrs: arrToObj(attrs),50         parent: parent,51         //关键属性52         children: [],53         text: null54       };55 56       //如果存在父节点,也标志下这个属于其子节点57       if(parent) {58         parent.children.push(node);59       }60       //还需要处理<br/> <input>这种非闭合标签61       //...62 63       //进入节点堆栈,当遇到弹出标签时候弹出64       stack.push(node)65       nodes.push(node);66 67       debugger;68     },69     end: function( tag ) { //标签结束70       //弹出当前子节点,根节点一定是最后弹出去的,兄弟节点之间会按顺序弹出,其父节点在最后一个子节点弹出后会被弹出71       stack.pop();72       debugger;73     },74     chars: function( text ) { //文本75       //如果是空格之类的不予处理76       if(text.trim() === '') return;77       let node = nodes[nodes.length - 1];78       //如果这里是表达式{{}}需要特殊处理79       if(node) node.text = text.trim()80       debugger;81     }82   });83 84   console.log(nodes)85 86 </script>87 88 </body>89 </html>

复制代码

这里输出了我们想要的结构:

第一个节点便是跟节点,我们可以根据他遍历整个节点,我们也可以根据数组(里面有对应的parent关系)生成我们想要的结构,可以看出借助强大的第三方工具库可以让我们的工作变得更加高效以及不容易出错,如果我们自己写上述HTMLParser会比较困难的,什么时候需要自己写什么时候需要借助,就要看你要做那个事情有没有现成确实可用的工具库了,第二步我们尝试下将这些模板标签,与data结合转换为真正的HTML结构

简单的Virtual DOM TO HTML

这里需要data加入了,我们简单实现一个MVVM的类,并且将上述Parser做成一个方法:

复制代码

  1 <!doctype html>  2 <html>  3 <head>  4   <title>起步</title>  5 </head>  6 <body>  7   8 <div id="app">  9  10 </div> 11  12 <script type="module"> 13  14   import HTMLParser from './src/core/parser/html-parser.js' 15  16   let html = ` 17 <div class="c-row search-line" data-flag="start" ontap="clickHandler"> 18   <div class="c-span9 js-start search-line-txt"> 19     {{name}}</div> 20     <input type="text"> 21      <br> 22 </div> 23   ` 24  25   function arrToObj(arr) { 26     let map = {}; 27     for(let i = 0, l = arr.length; i <  l; i++) { 28       map[arr[i].name] = arr[i].value 29     } 30     return map; 31   } 32  33   function htmlParser(html) { 34  35     //存储所有节点 36     let nodes = []; 37  38     //记录当前节点位置,方便定位parent节点 39     let stack = []; 40  41     HTMLParser(html, { 42       /* 43        unary: 是不是自闭和标签比如 <br/> input 44        attrs为属性的数组 45        */ 46       start: function( tag, attrs, unary ) { //标签开始 47         /* 48          stack记录的父节点,如果节点长度大于1,一定具有父节点 49          */ 50         let parent = stack.length ? stack[stack.length - 1] : null; 51  52         //最终形成的node对象 53         let node = { 54           //1标签, 2需要解析的表达式, 3 纯文本 55           type: 1, 56           tag: tag, 57           attrs: arrToObj(attrs), 58           parent: parent, 59           //关键属性 60           children: [] 61         }; 62  63         //如果存在父节点,也标志下这个属于其子节点 64         if(parent) { 65           parent.children.push(node); 66         } 67         //还需要处理<br/> <input>这种非闭合标签 68         //... 69  70         //进入节点堆栈,当遇到弹出标签时候弹出 71         stack.push(node) 72         nodes.push(node); 73  74 //      debugger; 75       }, 76       end: function( tag ) { //标签结束 77         //弹出当前子节点,根节点一定是最后弹出去的,兄弟节点之间会按顺序弹出,其父节点在最后一个子节点弹出后会被弹出 78         stack.pop(); 79  80 //      debugger; 81       }, 82       chars: function( text ) { //文本 83         //如果是空格之类的不予处理 84         if(text.trim() === '') return; 85         text = text.trim(); 86  87         //匹配 {{}} 拿出表达式 88         let reg = /\{\{(.*)\}\}/; 89         let node = nodes[nodes.length - 1]; 90         //如果这里是表达式{{}}需要特殊处理 91         if(!node) return; 92  93         if(reg.test(text)) { 94           node.children.push({ 95             type: 2, 96             expression: RegExp.$1, 97             text: text 98           }); 99         } else {100           node.children.push({101             type: 3,102             text: text103           });104         }105 //      debugger;106       }107     });108 109     return nodes;110 111   }112 113   class MVVM {114     /*115     暂时要求必须传入data以及el,其他事件什么的不管116 117     */118     constructor(opts) {119 120       //要求必须存在,这里不做参数校验了121       this.$el = typeof opts.el === 'string' ? document.getElementById(opts.el) : opts.el;122 123       //data必须存在,其他不做要求124       this.$data = opts.data;125 126       //模板必须存在127       this.$template = opts.template;128 129       //存放解析结束的虚拟dom130       this.$nodes = [];131 132       //将模板解析后,转换为一个函数133       this.$initRender();134 135       //渲染之136       this.$render();137 debugger;138     }139 140     $initRender() {141       let template = this.$template;142       let nodes = htmlParser(template);143       this.$nodes = nodes;144     }145 146     //解析模板生成的函数,将最总html结构渲染出来147     $render() {148 149       let data = this.$data;150       let root = this.$nodes[0];151       let parent = this._createEl(root);152       //简单遍历即可153 154       this._render(parent, root.children);155 156       this.$el.appendChild(parent);157     }158 159     _createEl(node) {160       let data = this.$data;161 162       let el = document.createElement(node.tag || 'span');163 164       for (let key in node.attrs) {165         el.setAttribute(key, node.attrs[key])166       }167 168       if(node.type === 2) {169         el.innerText = data[node.expression];170       } else if(node.type === 3) {171         el.innerText = node.text;172       }173 174       return el;175     }176     _render(parent, children) {177       let child = null;178       for(let i = 0, len = children.length; i < len; i++) {179         child = this._createEl(children[i]);180         parent.append(child);181         if(children[i].children) this._render(child, children[i].children);182       }183     }184 185 186   }187 188 189   let vm = new MVVM({190     el: 'app',191     template: html,192     data: {193       name: '叶小钗'194     }195   })196 197 198 199 200 </script>201 202 </body>203 </html>

复制代码

View Code

1 <div class="c-row search-line" data-flag="start" ontap="clickHandler">
<div class="c-span9 js-start search-line-txt"><span>叶小钗</span></div>
<input type="text">
</div>

这个代码非常简陋,只是对text部分做了处理,没有对属性,style等做处理,但是越是功能简单的代码理解起来越容易,后续的style以及属性大同小异,我们这里开始处理,介于篇幅,下次继续

原文出处:https://www.cnblogs.com/yexiaochai/p/9695298.html  

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消