zoukankan      html  css  js  c++  java
  • 一套代码小程序&Web&Native运行的探索03——处理模板及属性

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

    对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/mvvm

    我们在研究如果小程序在多端运行的时候,基本在前端框架这块陷入了困境,因为市面上没有框架可以直接拿来用,而Vue的相识度比较高,而且口碑很好,我们便接着这个机会同步学习Vue也解决我们的问题,我们看看这个系列结束后,会不会离目标进一点,后续如果实现后会重新整理系列文章......

    参考:

    https://github.com/fastCreator/MVVM(极度参考,十分感谢该作者,直接看Vue会比较吃力的,但是看完这个作者的代码便会轻易很多,可惜这个作者没有对应博客说明,不然就爽了)

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

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

    https://github.com/livoras/blog/issues/13

    上文中我们借助HTMLParser这种高级神器,终于将文本中的表达式替换了出来,这里单纯说文本这里也有以下问题:这段是不支持js代码的,+-、三元代码都不支持,所以以上都只是帮助我们理解,还是之前那句话,越是单纯的代码,越是考虑少的代码,可能越是能理解实现,但是后续仍然需要补足,我们这里还是要跟Vue对齐,这样做有个好处,当你不知道怎么做的时候,可以看看Vue的实现,当你思考这么做合不合适的时候,也可以参考Vue,那可是经过烈火淬炼的,值得深度学习,我们今天的任务比较简单便是完整的处理完style、属性以及表达式处理,这里我们直接在fastCreator这个作者下的源码开始学习,还有种学习源码的方法就是抄三次......

    我们学习的过程,先将代码写到一起方便理解,后续再慢慢拆分,首先是MVVM类,我们新建libs文件夹,先新建两个js文件,一个html-parser一个index(框架入口文件)

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

    这个时候我们的index代码量便下来了:

     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 MVVM from './libs/index.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   let vm = new MVVM({
    26     el: 'app',
    27     template: html,
    28     data: {
    29       name: '叶小钗'
    30     }
    31   })
    32 
    33 </script>
    34 </body>
    35 </html>

    我们现在来更改index.js入口文件的代码,这里特别说一下其中的$mount方法,他试试是要做一个这样的事情:

    //模板字符串
    <div id = "app">
      {{message}}
    </div>
    //render函数
    function anonymous() {
    with(this){return _h('div',{attrs:{"id":"app"}},["
      "+_s(message)+"
    "])}
    }

    将模板转换为一个函数render放到参数上,这里我们先简单实现,后续深入后我们重新翻下这个函数,修改后我们的index.js变成了这个样子:

      1 import HTMLParser from './html-parser.js'
      2 
      3 
      4 //工具函数 begin
      5 
      6 function isFunction(obj) {
      7   return typeof obj === 'function'
      8 }
      9 
     10 
     11 function makeAttrsMap(attrs, delimiters) {
     12   const map = {}
     13   for (let i = 0, l = attrs.length; i < l; i++) {
     14     map[attrs[i].name] = attrs[i].value;
     15   }
     16   return map;
     17 }
     18 
     19 
     20 
     21 //dom操作
     22 function query(el) {
     23   if (typeof el === 'string') {
     24     const selector = el
     25     el = document.querySelector(el)
     26     if (!el) {
     27       return document.createElement('div')
     28     }
     29   }
     30   return el
     31 }
     32 
     33 function cached(fn) {
     34   const cache = Object.create(null)
     35   return function cachedFn(str) {
     36     const hit = cache[str]
     37     return hit || (cache[str] = fn(str))
     38   }
     39 }
     40 
     41 let idToTemplate = cached(function (id) {
     42   var el = query(id)
     43   return el && el.innerHTML;
     44 })
     45 
     46 
     47 
     48 //工具函数 end
     49 
     50 //模板解析函数 begin
     51 
     52 const defaultTagRE = /{{((?:.|
    )+?)}}/g
     53 const regexEscapeRE = /[-.*+?^${}()|[]/\]/g
     54 
     55 const buildRegex = cached(delimiters => {
     56     const open = delimiters[0].replace(regexEscapeRE, '\$&')
     57     const close = delimiters[1].replace(regexEscapeRE, '\$&')
     58     return new RegExp(open + '((?:.|\n)+?)' + close, 'g')
     59   })
     60 
     61 
     62 function TextParser(text, delimiters) {
     63   const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
     64   if (!tagRE.test(text)) {
     65     return
     66   }
     67   const tokens = []
     68   let lastIndex = tagRE.lastIndex = 0
     69   let match, index
     70   while ((match = tagRE.exec(text))) {
     71     index = match.index
     72     // push text token
     73     if (index > lastIndex) {
     74       tokens.push(JSON.stringify(text.slice(lastIndex, index)))
     75     }
     76     // tag token
     77     const exp = match[1].trim()
     78     tokens.push(`_s(${exp})`)
     79     lastIndex = index + match[0].length
     80   }
     81   if (lastIndex < text.length) {
     82     tokens.push(JSON.stringify(text.slice(lastIndex)))
     83   }
     84   return tokens.join('+')
     85 }
     86 
     87 //******核心中的核心
     88 function compileToFunctions(template, vm) {
     89   let root;
     90   let currentParent;
     91   let options = vm.$options;
     92   let stack = [];
     93 
     94   //这段代码昨天做过解释,这里属性参数比昨天多一些
     95   HTMLParser(template, {
     96     start: function(tag, attrs, unary) {
     97 
     98       let element = {
     99         vm: vm,
    100         //1 标签 2 文本表达式 3 文本
    101         type: 1,
    102         tag,
    103         //数组
    104         attrsList: attrs,
    105         attrsMap: makeAttrsMap(attrs), //将属性数组转换为对象
    106         parent: currentParent,
    107         children: []
    108       };
    109 
    110       if(!root) {
    111         vm.$vnode = root = element;
    112       }
    113 
    114       if(currentParent && !element.forbidden) {
    115         currentParent.children.push(element);
    116         element.parent = currentParent;
    117       }
    118 
    119       if(!unary) {
    120         currentParent = element;
    121         stack.push(element);
    122       }
    123 
    124     },
    125     end: function (tag) {
    126       //获取当前元素
    127       let element = stack[stack.length - 1];
    128       let lastNode = element.children[element.children.length - 1];
    129       //删除最后一个空白节点,暂时感觉没撒用呢
    130       if(lastNode && lastNode.type === 3 && lastNode.text.trim === '') {
    131         element.children.pop();
    132       }
    133 
    134       //据说比调用pop节约性能相当于stack.pop()
    135       stack.length -= 1;
    136       currentParent = stack[stack.length - 1];
    137 
    138     },
    139     //处理真实的节点
    140     chars: function(text) {
    141       if (!text.trim()) {
    142         //text = ' '
    143         return;
    144       }
    145       //解析文本节点 exp: a{{b}}c => 'a'+_s(a)+'b'
    146       let expression = TextParser(text, options.delimiters)
    147       if (expression) {
    148         currentParent.children.push({
    149           type: 2,
    150           expression,
    151           text
    152         })
    153       } else {
    154         currentParent && currentParent.children.push({
    155           type: 3,
    156           text
    157         })
    158       }
    159     }
    160 
    161   });
    162 
    163   return root;
    164 
    165 }
    166 
    167 
    168 //模板解析函数 end
    169 
    170 //因为我们后面采用setData的方式通知更新,不做响应式更新,这里也先不考虑update,不考虑监控,先关注首次渲染
    171 //要做到更新数据,DOM跟着更新,事实上就是所有的data数据被监控(劫持)起来了,一旦更新都会调用对应的回调,我们这里做到更新再说
    172 function initData(vm, data) {
    173   if (isFunction(data)) {
    174     data = data()
    175   }
    176   vm.$data = data;
    177 }
    178 
    179 //全局数据保证每个MVVM实例拥有唯一id
    180 let uid = 0;
    181 
    182 export default class MVVM {
    183   constructor(options) {
    184     this.$options = options;
    185 
    186     //我们可以在传入参数的地方设置标签替换方式,比如可以设置为['<%=', '%>'],注意这里是数组
    187     this.$options.delimiters = this.$options.delimiters || ["{{", "}}"];
    188 
    189     //唯一标志
    190     this._uid = uid++;
    191 
    192     if(options.data) {
    193       //
    194       initData(this, options.data);
    195     }
    196 
    197     this.$mount(options.el);
    198 
    199   }
    200 
    201   //解析模板compileToFunctions,将之形成一个函数
    202   //很多网上的解释是将实例挂载到dom上,这里有些没明白,我们后面点再看看
    203   $mount(el) {
    204     let options = this.$options;
    205 
    206     el = el && query(el);
    207     this.$el = el;
    208 
    209     //如果用户自定义了render函数则不需要解析template
    210     //这里所谓的用户自定义,应该是用户生成了框架生成那坨代码,事实上还是将template转换为vnode
    211     if(!options.render) {
    212       let  template = options.template;
    213       if(template) {
    214         if(typeof template === 'string') {
    215           //获取script的template模板
    216           if (template[0] === '#') {
    217             template = idToTemplate(template)
    218           }
    219         } else if (template.nodeType) {
    220           //如果template是个dom结构,只能有一个根节点
    221           template = template.innerHTML;
    222         }
    223       }
    224 
    225       //上面的代码什么都没做,只是确保正确的拿到了template数据,考虑了各种情况
    226       //下面这段是关键,也是我们昨天干的事情
    227       if(template) {
    228         //***核心函数***/
    229         let render = compileToFunctions(template, this);
    230         options.render = render;
    231       }
    232 
    233 
    234     }
    235 
    236 
    237 
    238   }
    239 
    240 
    241 }
    242 
    243 //过去的代码
    244 function arrToObj(arr) {
    245   let map = {};
    246   for(let i = 0, l = arr.length; i <  l; i++) {
    247     map[arr[i].name] = arr[i].value
    248   }
    249   return map;
    250 }
    251 
    252 function htmlParser(html) {
    253 
    254   //存储所有节点
    255   let nodes = [];
    256 
    257   //记录当前节点位置,方便定位parent节点
    258   let stack = [];
    259 
    260   HTMLParser(html, {
    261     /*
    262      unary: 是不是自闭和标签比如 <br/> input
    263      attrs为属性的数组
    264      */
    265     start: function( tag, attrs, unary ) { //标签开始
    266       /*
    267        stack记录的父节点,如果节点长度大于1,一定具有父节点
    268        */
    269       let parent = stack.length ? stack[stack.length - 1] : null;
    270 
    271       //最终形成的node对象
    272       let node = {
    273         //1标签, 2需要解析的表达式, 3 纯文本
    274         type: 1,
    275         tag: tag,
    276         attrs: arrToObj(attrs),
    277         parent: parent,
    278         //关键属性
    279         children: []
    280       };
    281 
    282       //如果存在父节点,也标志下这个属于其子节点
    283       if(parent) {
    284         parent.children.push(node);
    285       }
    286       //还需要处理<br/> <input>这种非闭合标签
    287       //...
    288 
    289       //进入节点堆栈,当遇到弹出标签时候弹出
    290       stack.push(node)
    291       nodes.push(node);
    292 
    293 //      debugger;
    294     },
    295     end: function( tag ) { //标签结束
    296       //弹出当前子节点,根节点一定是最后弹出去的,兄弟节点之间会按顺序弹出,其父节点在最后一个子节点弹出后会被弹出
    297       stack.pop();
    298 
    299 //      debugger;
    300     },
    301     chars: function( text ) { //文本
    302       //如果是空格之类的不予处理
    303       if(text.trim() === '') return;
    304       text = text.trim();
    305 
    306       //匹配 {{}} 拿出表达式
    307       let reg = /{{(.*)}}/;
    308       let node = nodes[nodes.length - 1];
    309       //如果这里是表达式{{}}需要特殊处理
    310       if(!node) return;
    311 
    312       if(reg.test(text)) {
    313         node.children.push({
    314           type: 2,
    315           expression: RegExp.$1,
    316           text: text
    317         });
    318       } else {
    319         node.children.push({
    320           type: 3,
    321           text: text
    322         });
    323       }
    324 //      debugger;
    325     }
    326   });
    327 
    328   return nodes;
    329 
    330 }
    331 
    332 class MVVM1 {
    333   /*
    334    暂时要求必须传入data以及el,其他事件什么的不管
    335 
    336    */
    337   constructor(opts) {
    338 
    339     //要求必须存在,这里不做参数校验了
    340     this.$el = typeof opts.el === 'string' ? document.getElementById(opts.el) : opts.el;
    341 
    342     //data必须存在,其他不做要求
    343     this.$data = opts.data;
    344 
    345     //模板必须存在
    346     this.$template = opts.template;
    347 
    348     //存放解析结束的虚拟dom
    349     this.$nodes = [];
    350 
    351     //将模板解析后,转换为一个函数
    352     this.$initRender();
    353 
    354     //渲染之
    355     this.$render();
    356     debugger;
    357   }
    358 
    359   $initRender() {
    360     let template = this.$template;
    361     let nodes = htmlParser(template);
    362     this.$nodes = nodes;
    363   }
    364 
    365   //解析模板生成的函数,将最总html结构渲染出来
    366   $render() {
    367 
    368     let data = this.$data;
    369     let root = this.$nodes[0];
    370     let parent = this._createEl(root);
    371     //简单遍历即可
    372 
    373     this._render(parent, root.children);
    374 
    375     this.$el.appendChild(parent);
    376   }
    377 
    378   _createEl(node) {
    379     let data = this.$data;
    380 
    381     let el = document.createElement(node.tag || 'span');
    382 
    383     for (let key in node.attrs) {
    384       el.setAttribute(key, node.attrs[key])
    385     }
    386 
    387     if(node.type === 2) {
    388       el.innerText = data[node.expression];
    389     } else if(node.type === 3) {
    390       el.innerText = node.text;
    391     }
    392 
    393     return el;
    394   }
    395   _render(parent, children) {
    396     let child = null;
    397     for(let i = 0, len = children.length; i < len; i++) {
    398       child = this._createEl(children[i]);
    399       parent.append(child);
    400       if(children[i].children) this._render(child, children[i].children);
    401     }
    402   }
    403 
    404 
    405 }
    index.js

    这里仅仅是到输出vnode这步,接下来是将vnode转换为函数render,在写这段代码之前我们来说一说Vue中的render参数,事实上,我们new Vue的时候可以直接传递render参数:

     1 new Vue({
     2     render: function () {
     3         return this._h('div', {
     4             attrs:{
     5                 a: 'aaa'
     6             }
     7         }, [
     8            this._h('div')
     9         ])
    10     }
    11 })

    他对应的这段代码:

    1 new Vue({
    2     template: '<div class="aa">Hello World! </div>'
    3 })

    真实代码过程中的过程,以及我们上面代码的过程是,template 字符串 => 虚拟DOM对象 ast => 根据ast生成render函数......,这里又涉及到了另一个需要引用的工具库snabbdom

    snabbdom-render

    https://github.com/snabbdom/snabbdom,Vue2.0底层借鉴了snabdom,我们这里先重点介绍他的h函数,h(help帮助创建vnode)函数可以让我们轻松创建vnode,这里再对Virtual DOM做一个说明,这段话是我看到觉得很好的解释的话(https://github.com/livoras/blog/issues/13):

    我们一段js对象可以很容易的翻译为一段HTML代码:

     1 var element = {
     2   tagName: 'ul', // 节点标签名
     3   props: { // DOM的属性,用一个对象存储键值对
     4     id: 'list'
     5   },
     6   children: [ // 该节点的子节点
     7     {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
     8     {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
     9     {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
    10   ]
    11 }
    1 <ul id='list'>
    2   <li class='item'>Item 1</li>
    3   <li class='item'>Item 2</li>
    4   <li class='item'>Item 3</li>
    5 </ul>

    同样的,我们一段HTML代码其实属性、参数是很有限的,也十分轻易的能转换成一个js对象,我们如果使用dom操作改变了我们的html结构,事实上会形成一个新的js对象,这个时候我们将渲染后形成的js对象和渲染前形成的js对象进行对比,便可以清晰知道这次变化的差异部分,然后拿着差异部分的js对象(每个js对象都会映射到一个真实的dom对象)做更新即可,关于Virtual DOM文章作者对此做了一个总结:

    ① 用js对象表示DOM树结构,然后用这个js对象树结构生成一个真正的DOM树(document.create***操作),插入文档中(这个时候会形成render tree,看得到了)

    ② 当状态变化时(数据变化时),重新构造一颗新的对象树,和之前的作对比,记录差异部分

    ③ 将差异部分的数据更新到视图上,更新结束

    他这里描述的比较简单,事实上我们根据昨天的学习,可以知道框架事实上是劫持了没个数据对象,所以每个数据对象做了改变,会影响到哪些DOM结构是有记录的,这块我们后面章节再说,我们其实今天主要的目的还是处理文本和属性生成,却不想提前接触虚拟DOM了......

    其实我们之前的js对象element就已经可以代表一个虚拟dom了,之所以引入snabbddom应该是后面要处理diff部分,所以我们乖乖的学吧,首先我们定义一个节点的类:

    1 class Element {
    2   constructor(tagName, props, children) {
    3     this.tagName = tagName;
    4     this.props = props;
    5     this.children = children;
    6   }
    7 }

    上面的dom结构便可以变成这样了:

    1 new Element('ul', {id: 'list'}, [
    2   new Element('li', {class: 'item'}, ['Item 1']),
    3   new Element('li', {class: 'item'}, ['Item 2']),
    4   new Element('li', {class: 'item'}, ['Item 3'])
    5 ])

    似乎代码有点不好看,于是封装下实例化操作:

     1 class Element {
     2   constructor(tagName, props, children) {
     3     this.tagName = tagName;
     4     this.props = props;
     5     this.children = children;
     6   }
     7 }
     8 
     9 function el(tagName, props, children)  {
    10   return new Element(tagName, props, children)
    11 }
    12 
    13 el('ul', {id: 'list'}, [
    14   el('li', {class: 'item'}, ['Item 1']),
    15   el('li', {class: 'item'}, ['Item 2']),
    16   el('li', {class: 'item'}, ['Item 3'])
    17 ])

    然后就是根据这个js对象生成真正的DOM结构,也就是上面的html字符串:

     1 <!doctype html>
     2 <html>
     3 <head>
     4   <title>起步</title>
     5 </head>
     6 <body>
     7 
     8 <script type="text/javascript">
     9   //***虚拟dom部分代码,后续会换成snabdom
    10   class Element {
    11     constructor(tagName, props, children) {
    12       this.tagName = tagName;
    13       this.props = props;
    14       this.children = children;
    15     }
    16     render() {
    17       //拿着根节点往下面撸
    18       let root = document.createElement(this.tagName);
    19       let props = this.props;
    20 
    21       for(let name in props) {
    22         root.setAttribute(name, props[name]);
    23       }
    24 
    25       let children = this.children;
    26 
    27       for(let i = 0, l = children.length; i < l; i++) {
    28         let child = children[i];
    29         let childEl;
    30         if(child instanceof Element) {
    31           //递归调用
    32           childEl = child.render();
    33         } else {
    34           childEl = document.createTextNode(child);
    35         }
    36         root.append(childEl);
    37       }
    38 
    39       this.rootNode = root;
    40       return root;
    41     }
    42   }
    43 
    44   function el(tagName, props, children)  {
    45     return new Element(tagName, props, children)
    46   }
    47 
    48   let vnode = el('ul', {id: 'list'}, [
    49     el('li', {class: 'item'}, ['Item 1']),
    50     el('li', {class: 'item'}, ['Item 2']),
    51     el('li', {class: 'item'}, ['Item 3'])
    52   ])
    53 
    54   let root = vnode.render();
    55 
    56   document.body.appendChild(root);
    57 
    58 </script>
    59 
    60 </body>
    61 </html>

    饶了这么大一圈子,我们再回头看这段代码:

     1 new Vue({
     2     render: function () {
     3         return this._h('div', {
     4             attrs:{
     5                 a: 'aaa'
     6             }
     7         }, [
     8            this._h('div')
     9         ])
    10     }
    11 })

    这个时候,我们对这个_h干了什么,可能便有比较清晰的认识了,于是我们回到我们之前的代码,暂时跳出snabbdom

    解析模板

    在render中,我们有这么一段代码:

     1 //没有指令时运行,或者指令解析完毕
     2 function nodir(el) {
     3   let code
     4   //设置属性 等值
     5   const data = genData(el);
     6   //转换子节点
     7   const children = genChildren(el, true);
     8   code = `_h('${el.tag}'${
     9         data ? `,${data}` : '' // data
    10 }${
    11         children ? `,${children}` : '' // children
    12 })`
    13 return code
    14 }

    事实上这个跟上面那坨代码完成的工作差不多(同样的遍历加递归),只不过他这里还有更多的目的,比如这段代码最终会生成这样的:

    _h('div',{},[_h('div',{},["
        "+_s(name)]),_h('input',{}),_h('br',{})])

    这段代码会被包装成一个模板类,等待被实例化,显然到这里还没进入我们的模板解析过程,因为里面出现了_s(name),我们如果加一个span的话会变成这样:

    1 <div class="c-row search-line" data-flag="start" ontap="clickHandler">
    2   <div class="c-span9 js-start search-line-txt">
    3     {{name}}</div>
    4     <span>{{age+1}}</span>
    5     <input type="text">
    6      <br>
    7 </div>
    _h('div',{},[_h('div',{},["
        "+_s(name)]),_h('span',{},[_s(age+1)]),_h('input',{}),_h('br',{})])

    真实运行的时候这段代码是这个样子的:

     

    这段代码很纯粹,不包含属性和class,我们只需要处理文本内容替换即可,今天的任务比较简单,所以接下来的流程后便可以得出第一阶段代码:

     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 MVVM from './libs/index.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     <span>{{age+1}}</span>
    21     <input type="text">
    22      <br>
    23 </div>
    24   `
    25 
    26   let vm = new MVVM({
    27     el: '#app',
    28     template: html,
    29     data: {
    30       name: '叶小钗',
    31       age: 30
    32     }
    33   })
    34 
    35 </script>
    36 </body>
    37 </html>
      1 import HTMLParser from './html-parser.js'
      2 
      3 
      4 //工具函数 begin
      5 
      6 function isFunction(obj) {
      7   return typeof obj === 'function'
      8 }
      9 
     10 
     11 function makeAttrsMap(attrs, delimiters) {
     12   const map = {}
     13   for (let i = 0, l = attrs.length; i < l; i++) {
     14     map[attrs[i].name] = attrs[i].value;
     15   }
     16   return map;
     17 }
     18 
     19 
     20 
     21 //dom操作
     22 function query(el) {
     23   if (typeof el === 'string') {
     24     const selector = el
     25     el = document.querySelector(el)
     26     if (!el) {
     27       return document.createElement('div')
     28     }
     29   }
     30   return el
     31 }
     32 
     33 function cached(fn) {
     34   const cache = Object.create(null)
     35   return function cachedFn(str) {
     36     const hit = cache[str]
     37     return hit || (cache[str] = fn(str))
     38   }
     39 }
     40 
     41 let idToTemplate = cached(function (id) {
     42   var el = query(id)
     43   return el && el.innerHTML;
     44 })
     45 
     46 
     47 
     48 //工具函数 end
     49 
     50 //模板解析函数 begin
     51 
     52 const defaultTagRE = /{{((?:.|
    )+?)}}/g
     53 const regexEscapeRE = /[-.*+?^${}()|[]/\]/g
     54 
     55 const buildRegex = cached(delimiters => {
     56     const open = delimiters[0].replace(regexEscapeRE, '\$&')
     57     const close = delimiters[1].replace(regexEscapeRE, '\$&')
     58     return new RegExp(open + '((?:.|\n)+?)' + close, 'g')
     59   })
     60 
     61 
     62 function TextParser(text, delimiters) {
     63   const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
     64   if (!tagRE.test(text)) {
     65     return
     66   }
     67   const tokens = []
     68   let lastIndex = tagRE.lastIndex = 0
     69   let match, index
     70   while ((match = tagRE.exec(text))) {
     71     index = match.index
     72     // push text token
     73     if (index > lastIndex) {
     74       tokens.push(JSON.stringify(text.slice(lastIndex, index)))
     75     }
     76     // tag token
     77     const exp = match[1].trim()
     78     tokens.push(`_s(${exp})`)
     79     lastIndex = index + match[0].length
     80   }
     81   if (lastIndex < text.length) {
     82     tokens.push(JSON.stringify(text.slice(lastIndex)))
     83   }
     84   return tokens.join('+')
     85 }
     86 
     87 function makeFunction(code) {
     88   try {
     89     return new Function(code)
     90   } catch (e) {
     91     return function (){};
     92   }
     93 }
     94 
     95 //***虚拟dom部分代码,后续会换成snabdom
     96 class Element {
     97   constructor(tagName, props, children) {
     98     this.tagName = tagName;
     99     this.props = props;
    100     this.children = children || [];
    101   }
    102   render() {
    103     //拿着根节点往下面撸
    104     let el = document.createElement(this.tagName);
    105     let props = this.props;
    106 
    107     for(let name in props) {
    108       el.setAttribute(name, props[name]);
    109     }
    110 
    111     let children = this.children;
    112 
    113     for(let i = 0, l = children.length; i < l; i++) {
    114       let child = children[i];
    115       let childEl;
    116       if(child instanceof Element) {
    117         //递归调用
    118         childEl = child.render();
    119       } else {
    120         childEl = document.createTextNode(child);
    121       }
    122       el.append(childEl);
    123     }
    124     return el;
    125   }
    126 }
    127 
    128 function el(tagName, props, children)  {
    129   return new Element(tagName, props, children)
    130 }
    131 
    132 //***核心中的核心,将vnode转换为函数
    133 
    134 const simplePathRE = /^s*[A-Za-z_$][w$]*(?:.[A-Za-z_$][w$]*|['.*?']|[".*?"]|[d+]|[[A-Za-z_$][w$]*])*s*$/
    135 const modifierCode = {
    136   stop: '$event.stopPropagation();',
    137   prevent: '$event.preventDefault();',
    138   self: 'if($event.target !== $event.currentTarget)return;',
    139   ctrl: 'if(!$event.ctrlKey)return;',
    140   shift: 'if(!$event.shiftKey)return;',
    141   alt: 'if(!$event.altKey)return;',
    142   meta: 'if(!$event.metaKey)return;'
    143 }
    144 
    145 const keyCodes = {
    146   esc: 27,
    147   tab: 9,
    148   enter: 13,
    149   space: 32,
    150   up: 38,
    151   left: 37,
    152   right: 39,
    153   down: 40,
    154   'delete': [8, 46]
    155 }
    156 
    157 
    158 function codeGen(ast) {
    159   //解析成h render字符串形式
    160   const code = ast ? genElement(ast) : '_h("div")'
    161   //把render函数,包起来,使其在当前作用域内
    162   return makeFunction(`with(this){ debugger; return ${code}}`)
    163 }
    164 
    165 function genElement(el) {
    166   //无指令
    167   return nodir(el)
    168 }
    169 
    170 //没有指令时运行,或者指令解析完毕
    171 function nodir(el) {
    172   let code
    173   //设置属性 等值
    174   const data = genData(el);
    175   //转换子节点
    176   const children = genChildren(el, true);
    177   code = `_h('${el.tag}'${
    178         data ? `,${data}` : '' // data
    179 }${
    180         children ? `,${children}` : '' // children
    181 })`
    182 return code
    183 }
    184 
    185 function genChildren(el, checkSkip) {
    186   const children = el.children
    187   if (children.length) {
    188     const el = children[0]
    189     // 如果是v-for
    190     //if (children.length === 1 && el.for) {
    191     //  return genElement(el)
    192     //}
    193     const normalizationType = 0
    194     return `[${children.map(genNode).join(',')}]${
    195             checkSkip
    196                 ? normalizationType ? `,${normalizationType}` : ''
    197   : ''
    198   }`
    199 }
    200 }
    201 
    202 function genNode(node) {
    203   if (node.type === 1) {
    204     return genElement(node)
    205   } else {
    206     return genText(node)
    207   }
    208 }
    209 
    210 function genText(text) {
    211   return text.type === 2 ? text.expression : JSON.stringify(text.text)
    212 }
    213 
    214 function genData(el) {
    215   let data = '{'
    216   // attributes
    217   if (el.style) {
    218     data += 'style:' + genProps(el.style) + ','
    219   }
    220   if (Object.keys(el.attrs).length) {
    221     data += 'attrs:' + genProps(el.attrs) + ','
    222   }
    223   if (Object.keys(el.props).length) {
    224     data += 'props:' + genProps(el.props) + ','
    225   }
    226   if (Object.keys(el.events).length) {
    227     data += 'on:' + genProps(el.events) + ','
    228   }
    229   if (Object.keys(el.hook).length) {
    230     data += 'hook:' + genProps(el.hook) + ','
    231   }
    232   data = data.replace(/,$/, '') + '}'
    233   return data
    234 }
    235 
    236 function genProps(props) {
    237   let res = '{';
    238   for (let key in props) {
    239     res += `"${key}":${props[key]},`
    240   }
    241   return res.slice(0, -1) + '}'
    242 }
    243 
    244 //******核心中的核心
    245 function compileToFunctions(template, vm) {
    246   let root;
    247   let currentParent;
    248   let options = vm.$options;
    249   let stack = [];
    250 
    251   //这段代码昨天做过解释,这里属性参数比昨天多一些
    252   HTMLParser(template, {
    253     start: function(tag, attrs, unary) {
    254 
    255       let element = {
    256         vm: vm,
    257         //1 标签 2 文本表达式 3 文本
    258         type: 1,
    259         tag,
    260         //数组
    261         attrsList: attrs,
    262         attrsMap: makeAttrsMap(attrs), //将属性数组转换为对象
    263         parent: currentParent,
    264         children: [],
    265 
    266         //下面这些属性先不予关注,因为底层函数没有做校验,不传要报错
    267         events: {},
    268         style: null,
    269         hook: {},
    270         props: {},//DOM属性
    271         attrs: {}//值为true,false则移除该属性
    272 
    273       };
    274 
    275       if(!root) {
    276         vm.$vnode = root = element;
    277       }
    278 
    279       if(currentParent && !element.forbidden) {
    280         currentParent.children.push(element);
    281         element.parent = currentParent;
    282       }
    283 
    284       if(!unary) {
    285         currentParent = element;
    286         stack.push(element);
    287       }
    288 
    289     },
    290     end: function (tag) {
    291       //获取当前元素
    292       let element = stack[stack.length - 1];
    293       let lastNode = element.children[element.children.length - 1];
    294       //删除最后一个空白节点,暂时感觉没撒用呢
    295       if(lastNode && lastNode.type === 3 && lastNode.text.trim === '') {
    296         element.children.pop();
    297       }
    298 
    299       //据说比调用pop节约性能相当于stack.pop()
    300       stack.length -= 1;
    301       currentParent = stack[stack.length - 1];
    302 
    303     },
    304     //处理真实的节点
    305     chars: function(text) {
    306       if (!text.trim()) {
    307         //text = ' '
    308         return;
    309       }
    310       //解析文本节点 exp: a{{b}}c => 'a'+_s(a)+'b'
    311       let expression = TextParser(text, options.delimiters)
    312       if (expression) {
    313         currentParent.children.push({
    314           type: 2,
    315           expression,
    316           text
    317         })
    318       } else {
    319         currentParent && currentParent.children.push({
    320           type: 3,
    321           text
    322         })
    323       }
    324     }
    325 
    326   });
    327 
    328   //***关键代码***
    329   //将vnode转换为render函数,事实上可以直接传入这种render函数,便不会执行这块逻辑,编译时候会把这块工作做掉
    330   return codeGen(root);
    331 
    332 }
    333 
    334 
    335 //模板解析函数 end
    336 
    337 //因为我们后面采用setData的方式通知更新,不做响应式更新,这里也先不考虑update,不考虑监控,先关注首次渲染
    338 //要做到更新数据,DOM跟着更新,事实上就是所有的data数据被监控(劫持)起来了,一旦更新都会调用对应的回调,我们这里做到更新再说
    339 function initData(vm, data) {
    340   if (isFunction(data)) {
    341     data = data()
    342   }
    343 
    344   //这里将data上的数据移植到this上,后面要监控
    345   for(let key in data) {
    346 
    347     //这里有可能会把自身方法覆盖,所以自身的属性方法需要+$
    348     vm[key] = data[key];
    349   }
    350 
    351   vm.$data = data;
    352 }
    353 
    354 //全局数据保证每个MVVM实例拥有唯一id
    355 let uid = 0;
    356 
    357 export default class MVVM {
    358   constructor(options) {
    359     this.$options = options;
    360 
    361     //我们可以在传入参数的地方设置标签替换方式,比如可以设置为['<%=', '%>'],注意这里是数组
    362     this.$options.delimiters = this.$options.delimiters || ["{{", "}}"];
    363 
    364     //唯一标志
    365     this._uid = uid++;
    366 
    367     if(options.data) {
    368       //
    369       initData(this, options.data);
    370     }
    371 
    372     this.$mount(options.el);
    373 
    374     let _node = this._render().render();
    375     this.$el.appendChild( _node)
    376 
    377   }
    378 
    379   //解析模板compileToFunctions,将之形成一个函数
    380   //很多网上的解释是将实例挂载到dom上,这里有些没明白,我们后面点再看看
    381   $mount(el) {
    382     let options = this.$options;
    383 
    384     el = el && query(el);
    385     this.$el = el;
    386 
    387     //如果用户自定义了render函数则不需要解析template
    388     //这里所谓的用户自定义,应该是用户生成了框架生成那坨代码,事实上还是将template转换为vnode
    389     if(!options.render) {
    390       let  template = options.template;
    391       if(template) {
    392         if(typeof template === 'string') {
    393           //获取script的template模板
    394           if (template[0] === '#') {
    395             template = idToTemplate(template)
    396           }
    397         } else if (template.nodeType) {
    398           //如果template是个dom结构,只能有一个根节点
    399           template = template.innerHTML;
    400         }
    401       }
    402 
    403       //上面的代码什么都没做,只是确保正确的拿到了template数据,考虑了各种情况
    404       //下面这段是关键,也是我们昨天干的事情
    405       if(template) {
    406         //***核心函数***/
    407         let render = compileToFunctions(template, this);
    408         options.render = render;
    409       }
    410     }
    411 
    412     return this;
    413   }
    414 
    415   _render() {
    416     let render = this.$options.render
    417     let vnode
    418     try {
    419       //自动解析的template不需要h,用户自定义的函数需要h
    420       vnode = render.call(this, this._h);
    421     } catch (e) {
    422       warn(`render Error : ${e}`)
    423     }
    424     return vnode
    425   }
    426 
    427   _h(tag, data, children) {
    428     return el(tag, data, children)
    429   }
    430 
    431   _s(val) {
    432     return val == null
    433       ? ''
    434       : typeof val === 'object'
    435       ? JSON.stringify(val, null, 2)
    436       : String(val)
    437   }
    438 
    439 }
    libs/index.js

    之前我们图简单,一直没有解决属性问题,现在我们在模板里面加入一些属性:

    1 <div class="c-row search-line" data-name="{{name}}" data-flag="start" ontap="clickHandler">
    2   <div class="c-span9 js-start search-line-txt">
    3     {{name}}</div>
    4     <span>{{age+1}}</span>
    5     <input type="text" value="{{age}}">
    6      <br>
    7 </div>

    情况就变得有所不同了,这里多加一句:

     1 setElAttrs(el, delimiters)
     2 //==>
     3 function setElAttrs(el, delimiters) {
     4   var s = delimiters[0], e = delimiters[1];
     5   var reg = new RegExp(`^${s}(.+)${e}$`);
     6   var attrs = el.attrsMap;
     7   for (let key in attrs) {
     8     let value = attrs[key];
     9     var match = value.match(reg)
    10     if (match) {
    11       value = match[1];
    12       if (isAttr(key)) {
    13         el.props[key] = '_s('+value+')';
    14       } else {
    15         el.attrs[key] = value;
    16       }
    17     } else {
    18       if (isAttr(key)) {
    19         el.props[key] = "'" + value + "'";
    20       } else {
    21         el.attrs[key] = "'" + value + "'";
    22       }
    23     }
    24 
    25   }
    26 }

    这段代码会处理所有的属性,如果是属性中包含“{{}}”关键词,便会替换,不是我们的属性便放到attrs中,是的就放到props中,这里暂时不太能区分为什么要分为attrs何props,后续我们这边给出代码,于是我们的index.js变成了这个样子:

    libs/index.js
    _h('div',{attrs:{"data-name":name,"data-flag":'start',"ontap":'clickHandler'},props:{"class":'c-row search-line'}},
    [_h('div',{props:{"class":'c-span9 js-start search-line-txt'}},
    [" "+_s(name)]),_h('span',{},
    [_s(age+1)]),_h('input',{props:{"type":'text',"value":_s(age)}}),_h('br',{})])
    1 <div id="app">
    2   <div class="c-row search-line" data-name="叶小钗" data-flag="start" ontap="clickHandler">
    3     <div class="c-span9 js-start search-line-txt">
    4       叶小钗</div>
    5     <span>31</span>
    6     <input type="text" value="30">
    7     <br>
    8   </div>
    9 </div>

    然后我们来处理class以及style,他们是需要特殊处理的:

    <div class="c-row search-line {{name}} {{age}}" style="font-size: 14px; margin-left: {{age}}px " data-name="{{name}}" 
    data-flag="start" ontap="clickHandler"> <div class="c-span9 js-start search-line-txt"> {{name}}</div> <span>{{age+1}}</span> <input type="text" value="{{age}}"> <br> </div>
    libs/index.js

    生成了如下代码:

    1 <div class="c-row search-line 叶小钗 30" data-name="叶小钗" data-flag="start" ontap="clickHandler" style="font-size:  14px; margin-left:  30px ;">
    2   <div class="c-span9 js-start search-line-txt">
    3     叶小钗</div>
    4   <span>31</span>
    5   <input type="text" value="30">
    6   <br>
    7 </div>

    虽然这段代码能运行,无论如何我们的属性和class也展示出来了,但是问题却不少:

    ① 这段代码仅仅就是为了运行,或者说帮助我们理解

    ② libs/index.js代码已经超过了500行,维护起来有点困难了,连我自己都有时候找不到东西,所以我们该分拆文件了

    于是,我们暂且忍受这段说明性(演示性)代码,将之进行文件分拆

    文件分拆

    文件拆分后代码顺便传到了github上:https://github.com/yexiaochai/wxdemo/tree/master/mvvm

    这里简单的解释下各个文件是干撒的:

     1 ./libs
     2 ..../codegen.js 代码生成器,传入一个ast(js树对象),转换为render函数
     3 ..../helps.js 处理vnode的相关工具函数,比如处理属性节点,里面的生成函数感觉该放到utils中
     4 ..../html-parser.js 第三方库,HTML解析神器,帮助生成js dom树对象
     5 ..../instance.js 初始化mvvm实例工具类
     6 ..../mvvm.js 入口函数
     7 ..../parser.js 模板解析生成render函数,核心
     8 ..../text-parser.js 工具类,将{{}}做替换生成字符串
     9 ..../utils.js 工具库
    10 ..../vnode.js 虚拟树库,暂时自己写的,后续要换成snabbdom
    11 ./index.html 入口文件

    今天的学习到此位置,明天我们来处理数据更新相关

  • 相关阅读:
    NanUI for Winform发布,让Winform界面设计拥有无限可能
    使用Nginx反向代理 让IIS和Tomcat等多个站点一起飞
    jQuery功能强大的图片查看器插件
    Entity Framework 5.0 Code First全面学习
    封装EF code first用存储过程的分页方法
    NET中使用Redis (二)
    Redis学习笔记~Redis主从服务器,读写分离
    ID3算法
    定性归纳(1)
    js加密
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/9699698.html
Copyright © 2011-2022 走看看