一套代碼小程序&Web&Native運行的探索03——處理模板及屬性

接上文: 一套代碼小程序&Web&Native運行的探索02javascript

對應Git代碼地址請見:https://github.com/yexiaochai/wxdemo/tree/master/mvvmhtml

咱們在研究若是小程序在多端運行的時候,基本在前端框架這塊陷入了困境,由於市面上沒有框架能夠直接拿來用,而Vue的相識度比較高,並且口碑很好,咱們便接着這個機會同步學習Vue也解決咱們的問題,咱們看看這個系列結束後,會不會離目標進一點,後續若是實現後會從新整理系列文章......前端

參考:java

https://github.com/fastCreator/MVVM(極度參考,十分感謝該做者,直接看Vue會比較吃力的,可是看完這個做者的代碼便會輕易不少,惋惜這個做者沒有對應博客說明,否則就爽了)node

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

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

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

上文中咱們藉助HTMLParser這種高級神器,終於將文本中的表達式替換了出來,這裏單純說文本這裏也有如下問題:這段是不支持js代碼的,+-、三元代碼都不支持,因此以上都只是幫助咱們理解,仍是以前那句話,越是單純的代碼,越是考慮少的代碼,可能越是能理解實現,可是後續仍然須要補足,咱們這裏仍是要跟Vue對齊,這樣作有個好處,當你不知道怎麼作的時候,能夠看看Vue的實現,當你思考這麼作合不合適的時候,也能夠參考Vue,那但是通過烈火淬鍊的,值得深度學習,咱們今天的任務比較簡單即是完整的處理完style、屬性以及表達式處理,這裏咱們直接在fastCreator這個做者下的源碼開始學習,還有種學習源碼的方法就是抄三次......canvas

咱們學習的過程,先將代碼寫到一塊兒方便理解,後續再慢慢拆分,首先是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(/<!--([\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);
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',{},["\n    "+_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',{},["\n    "+_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 = /\{\{((?:.|\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 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'}},
["\n "+_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 入口文件

今天的學習到此位置,明天咱們來處理數據更新相關

相關文章
相關標籤/搜索