1. 程式人生 > >一套程式碼小程式&Web&Native執行的探索03

一套程式碼小程式&Web&Native執行的探索03

我們在研究如果小程式在多端執行的時候,基本在前端框架這塊陷入了困境,因為市面上沒有框架可以直接拿來用,而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.jsindex.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(/<!--([\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 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"}},["\n  "+_s(message)+"\n"])}
}

將模板轉換為一個函式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 = /\{\{((?:.|\n)+?)\}\}/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);