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

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

接上文:一套程式碼小程式&Web&Native執行的探索01,本文都是一些探索性為目的的研究學習,在最終版輸出前,內中的內容可能會有點亂

參考:

https://github.com/fastCreator/MVVM

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

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

經過之前的學習,發現Vue其實與小程式框架相識度比較高,業內也有mpvue這種還比較成熟的方案了,我們這邊依舊不著急去研究成熟的框架,現在看看自己能做到什麼程度,最近也真正的開始接觸了一些Vue的東西,裡面的程式碼真的非常不錯,研究學習了下Vue的結構,發現其實跟我們要的很類似,這裡想要嘗試初步的方案:提供Html模板->解析Html模板,其實這裡就是Vue裡面Parse部分的邏輯,一小部分程式碼,這樣有很多Vue的程式碼可以借鑑,也變相的學習Vue的原始碼,一舉兩得,於是我們速度開始今天的學習

首先,我們設定一個簡單的目標:設定一段簡單的小程式模板,當我們做完web版本後,他可以在小程式中執行

<view class="c-row search-line" data-flag="start" ontap="clickHandler">
  <view class="c-span9 js-start search-line-txt">
    {{name}}</view>
</view>
 1 Page({
 2   data: {
 3     name: 'hello world'
 4   },
 5
clickHandler: function () { 6 this.setData({ 7 name: '葉小釵' 8 }) 9 } 10 })

這裡第一個關鍵便是將html模板轉換為js程式碼,如果是之前我們直接會用這種程式碼:

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

將上述程式碼做字串處理成字串函式,然後將data傳入,重新渲染即可。然而技術在變化,在進步。試想我們一個頁面某個子節點文字發生了變化,全部重新渲染似乎不太划算,於是出現了虛擬DOM概念(React 導致其流行),他出現的意義就是之前我們使用jQuery操作10次dom的時候瀏覽器會操作10次,這裡render過程中導致的座標計算10次render tree的形成可能讓頁面變得越來越卡,而虛擬DOM能很好的解決這一切,所以這裡我們就需要將我們模板中的程式碼首先轉換為虛擬DOM,這裡涉及到了複雜的解析過程

PS:回到最初Server渲染時代,每次點選就會導致一次伺服器互動,並且重新渲染頁面

Virtual DOM

我們做的第一步就是將模板html字串轉換為js物件,這個程式碼都不要說去實現,光是想想就知道里面必定會有大量的正則,大量的細節要處理,但我們的目標是一套程式碼多端執行,完全沒(能力)必要在這種地方耗費時間,所以我們直接閱讀這段程式碼:https://johnresig.com/blog/pure-javascript-html-parser/,稍作更改後,便可以得到以下程式碼:

  1 /*
  2  * Modified at https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
  3  */
  4 
  5 // Regular Expressions for parsing tags and attributes
  6 let startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:@][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  7     endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/,
  8     attr = /([a-zA-Z_:@][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g
  9 
 10 // Empty Elements - HTML 5
 11 let empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr")
 12 
 13 // Block Elements - HTML 5
 14 let block = makeMap("a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video")
 15 
 16 // Inline Elements - HTML 5
 17 let inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var")
 18 
 19 // Elements that you can, intentionally, leave open
 20 // (and which close themselves)
 21 let closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr")
 22 
 23 // Attributes that have their values filled in disabled="disabled"
 24 let fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected")
 25 
 26 // Special Elements (can contain anything)
 27 let special = makeMap("script,style")
 28 
 29 function makeMap(str) {
 30     var obj = {}, items = str.split(",");
 31     for (var i = 0; i < items.length; i++)
 32         obj[items[i]] = true;
 33     return obj;
 34 }
 35 
 36 export default function HTMLParser(html, handler) {
 37     var index, chars, match, stack = [], last = html;
 38     stack.last = function () {
 39         return this[this.length - 1];
 40     };
 41 
 42     while (html) {
 43         chars = true;
 44 
 45         // Make sure we're not in a script or style element
 46         if (!stack.last() || !special[stack.last()]) {
 47 
 48             // Comment
 49             if (html.indexOf("<!--") == 0) {
 50                 index = html.indexOf("-->");
 51 
 52                 if (index >= 0) {
 53                     if (handler.comment)
 54                         handler.comment(html.substring(4, index));
 55                     html = html.substring(index + 3);
 56                     chars = false;
 57                 }
 58 
 59                 // end tag
 60             } else if (html.indexOf("</") == 0) {
 61                 match = html.match(endTag);
 62 
 63                 if (match) {
 64                     html = html.substring(match[0].length);
 65                     match[0].replace(endTag, parseEndTag);
 66                     chars = false;
 67                 }
 68 
 69                 // start tag
 70             } else if (html.indexOf("<") == 0) {
 71                 match = html.match(startTag);
 72 
 73                 if (match) {
 74                     html = html.substring(match[0].length);
 75                     match[0].replace(startTag, parseStartTag);
 76                     chars = false;
 77                 }
 78             }
 79 
 80             if (chars) {
 81                 index = html.indexOf("<");
 82 
 83                 var text = index < 0 ? html : html.substring(0, index);
 84                 html = index < 0 ? "" : html.substring(index);
 85 
 86                 if (handler.chars)
 87                     handler.chars(text);
 88             }
 89 
 90         } else {
 91             html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function (all, text) {
 92                 text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, "$1$2");
 93                 if (handler.chars)
 94                     handler.chars(text);
 95 
 96                 return "";
 97             });
 98 
 99             parseEndTag("", stack.last());
100         }
101 
102         if (html == last)
103             throw "Parse Error: " + html;
104         last = html;
105     }
106 
107     // Clean up any remaining 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 };
View Code

這是一段非常牛逼的程式碼,要寫出這種程式碼需要花很多功夫,繞過很多細節,自己寫很難還未必寫得好,所以拿來用就好,不必愧疚......,但是我們需要知道這段程式碼幹了什麼:

他會遍歷我們的字串模板,解析後會有四個回撥可供使用:start、end、chars、comment,我們要做的就是填充裡面的事件,完成我們將HTML轉換為js物件的工作:

 1 <!doctype html>
 2 <html>
 3 <head>
 4   <title>起步</title>
 5 </head>
 6 <body>
 7 
 8 <script type="module">
 9 
10   import HTMLParser from './src/core/parser/html-parser.js'
11 
12   let html = `
13 <div class="c-row search-line" data-flag="start" ontap="clickHandler">
14   <div class="c-span9 js-start search-line-txt">
15     {{name}}</div>
16 </div>
17   `
18 
19   function arrToObj(arr) {
20     let map = {};
21     for(let i = 0, l = arr.length; i <  l; i++) {
22       map[arr[i].name] = arr[i].value
23     }
24     return map;
25   }
26 
27   //儲存所有節點
28   let nodes = [];
29 
30   //記錄當前節點位置,方便定位parent節點
31   let stack = [];
32 
33   HTMLParser(html, {
34     /*
35      unary: 是不是自閉和標籤比如 <br/> input
36      attrs為屬性的陣列
37     */
38     start: function( tag, attrs, unary ) { //標籤開始
39       /*
40        stack記錄的父節點,如果節點長度大於1,一定具有父節點
41        */
42       let parent = stack.length ? stack[stack.length - 1] : null;
43 
44       //最終形成的node物件
45       let node = {
46         //1標籤, 2需要解析的表示式, 3 純文字
47         type: 1,
48         tag: tag,
49         attrs: arrToObj(attrs),
50         parent: parent,
51         //關鍵屬性
52         children: [],
53         text: null
54       };
55 
56       //如果存在父節點,也標誌下這個屬於其子節點
57       if(parent) {
58         parent.children.push(node);
59       }
60       //還需要處理<br/> <input>這種非閉合標籤
61       //...
62 
63       //進入節點堆疊,當遇到彈出標籤時候彈出
64       stack.push(node)
65       nodes.push(node);
66 
67       debugger;
68     },
69     end: function( tag ) { //標籤結束
70       //彈出當前子節點,根節點一定是最後彈出去的,兄弟節點之間會按順序彈出,其父節點在最後一個子節點彈出後會被彈出
71       stack.pop();
72       debugger;
73     },
74     chars: function( text ) { //文字
75       //如果是空格之類的不予處理
76       if(text.trim() === '') return;
77       let node = nodes[nodes.length - 1];
78       //如果這裡是表示式{{}}需要特殊處理
79       if(node) node.text = text.trim()
80       debugger;
81     }
82   });
83 
84   console.log(nodes)
85 
86 </script>
87 
88 </body>
89 </html>

這裡輸出了我們想要的結構:

第一個節點便是跟節點,我們可以根據他遍歷整個節點,我們也可以根據陣列(裡面有對應的parent關係)生成我們想要的結構,可以看出藉助強大的第三方工具庫可以讓我們的工作變得更加高效以及不容易出錯,如果我們自己寫上述HTMLParser會比較困難的,什麼時候需要自己寫什麼時候需要藉助,就要看你要做那個事情有沒有現成確實可用的工具庫了,第二步我們嘗試下將這些模板標籤,與data結合轉換為真正的HTML結構

簡單的Virtual DOM TO HTML

這裡需要data加入了,我們簡單實現一個MVVM的類,並且將上述Parser做成一個方法:

  1 <!doctype html>
  2 <html>
  3 <head>
  4   <title>起步</title>
  5 </head>
  6 <body>
  7 
  8 <div id="app">
  9 
 10 </div>
 11 
 12 <script type="module">
 13 
 14   import HTMLParser from './src/core/parser/html-parser.js'
 15 
 16   let html = `
 17 <div class="c-row search-line" data-flag="start" ontap="clickHandler">
 18   <div class="c-span9 js-start search-line-txt">
 19     {{name}}</div>
 20     <input type="text">
 21      <br>
 22 </div>
 23   `
 24 
 25   function arrToObj(arr) {
 26     let map = {};
 27     for(let i = 0, l = arr.length; i <  l; i++) {
 28       map[arr[i].name] = arr[i].value
 29     }
 30     return map;
 31   }
 32 
 33   function htmlParser(html) {
 34 
 35     //儲存所有節點
 36     let nodes = [];
 37 
 38     //記錄當前節點位置,方便定位parent節點
 39     let stack = [];
 40 
 41     HTMLParser(html, {
 42       /*
 43        unary: 是不是自閉和標籤比如 <br/> input
 44        attrs為屬性的陣列
 45        */
 46       start: function( tag, attrs, unary ) { //標籤開始
 47         /*
 48          stack記錄的父節點,如果節點長度大於1,一定具有父節點
 49          */
 50         let parent = stack.length 
            
           

相關推薦

程式碼程式&Web&Native執行探索02

接上文:一套程式碼小程式&Web&Native執行的探索01,本文都是一些探索性為目的的研究學習,在最終版輸出前,內中的內容可能會有點亂 參考: https://github.com/fastCreator/MVVM https://www.tangshuang.net/3756.htm

程式碼程式&Web&Native執行探索(1)

前言 之前一直在跟業務方打交道後面研究了下後端,期間還做了一些運營、管理相關工作,哈哈,最近一年工作經歷十分豐富啊,生命在於不斷的嘗試嘛。 當然,不可避免的在前端技術一塊也稍微有點落後,對React&Vue沒有進行過深入一點的研究,這裡得空我們便來一起研究一番(回想起來寫程式碼的日子才是最快樂的

程式碼程式&Web&Native執行探索03

我們在研究如果小程式在多端執行的時候,基本在前端框架這塊陷入了困境,因為市面上沒有框架可以直接拿來用,而Vue的相識度比較高,而且口碑很好,我們便接著這個機會同步學習Vue也解決我們的問題,我們看看這個系列結束後,會不會離目標進一點,後續如果實現後會重新整理系列文章...... 參考: https:/

程式碼程式&Web&Native執行探索(2)

接上文:一套程式碼小程式&Web&Native執行的探索01,本文都是一些探索性為目的的研究學習,在最終版輸出前,內中的內容可能會有點亂 參考: https://github.com/fastCreator/MVVM https://www.tangshuang.net/3756.html

程式碼程式&Web&Native執行探索04——資料更新

參考: https://github.com/fastCreator/MVVM(極度參考,十分感謝該作者,直接看Vue會比較吃力的,但是看完這個作者的程式碼便會輕易很多,可惜這個作者沒有對應部落格說明,不然就爽了) https://www.tangshuang.net/3756.html htt

程式碼程式&Web&Native執行探索05——snabbdom

參考: https://github.com/fastCreator/MVVM(極度參考,十分感謝該作者,直接看Vue會比較吃力的,但是看完這個作者的程式碼便會輕易很多,可惜這個作者沒有對應部落格說明,不然就爽了) https://www.tangshuang.net/3756.html h

程式碼程式&Web&Native執行探索07——mpvue簡單調研

前言 最近工作比較忙,加之上個月生了小孩,小情人是各種折騰他爸媽,我們可以使用的獨立時間片不多,雖然這塊研究進展緩慢,但是一直做下去,肯定還是會有一些收穫的 之前我們這個課題研究一直是做獨立的研究,沒有去看已有的解決方案,這個是為了保證一個自己獨立的思維,無論獨立的思維還是人格都是很重要的東西,然獨學

程式碼程式&Native&Web階段總結篇】可以這樣閱讀Vue原始碼

前言 在實際程式碼過程中我們發現,我們可能又要做H5站又要做小程式同時還要做個APP,這裡會造成很大的資源浪費,如果設定一個規則,讓我們可以先寫H5程式碼,然後將小程式以及APP的業務差異程式碼做掉,豈不快哉?但小程式的web框架並不開源,不然也用不著我們在此費力了,經過研究,小程式web端框架是一套自

uni-app 1.4 釋出,程式碼,發行程式(微信/支付寶/百度)、H5、App多個平臺

在2019新年到來之際,uni-app 1.4版本正式釋出,新增支援百度、支付寶小程式,開放外掛市場,同時注入更多優秀特性,為開發者送上了一份新年大禮! 支援更多小程式平臺 uni-app 1.4 版本新增支援百度、支付寶小程式,從此一次開發,可釋出小程式(微信/支付寶/百度)、H5、App(iOS/An

uni-app 是一個使用 Vue.js 開發跨平臺應用的前端框架,開發者編寫程式碼,可編譯到iOS、Android、微信程式等多個平臺。

uni-app 是一個使用 Vue.js 開發跨平臺應用的前端框架,開發者編寫一套程式碼,可編譯到iOS、Android、微信小程式等多個平臺。 uni-app在跨端數量、擴充套件能力、效能體

React Native 中為IOS和Android設定不同的Style樣式,程式碼解決雙端顯示

React Native 開發中,大多數的元件都是IOS和Android通用的,包括大量的功能性程式碼,至少有80%以上的程式碼可以複用,而剩下的一些元件樣式/少量的程式碼會需要區分雙端,但是為了這少量的程式碼把IOS和Android完全區分這明顯不合適,程式碼複用性下降,程式碼維護量上升

微信程式在掃掃進入程式的時候 安卓手機後臺繼續執行的常規處理

一般在掃一掃進入小程式是沒什麼問題的 關於這個話題就不多說 怎麼去配置後臺 我的部落格裡面有提到 有興趣的可以去看看 現在是使用者第一次掃碼進入之後 退出小程式 在外部掃一掃進入小程式指定介面的時候 安卓手機就出現了一個問題 它會閃爍一下 返回的首頁去 而不是我們想要的 因為安卓手機有一個常規的操作 就是微信

Android 和IOS 混合開發,程式碼兩處執行-----Flutter

轉載自:https://www.jianshu.com/p/8baa8ed2414d什麼是Flutter2018年2月27日,在2018世界移動大會上,Google釋出了Flutter的第一個Beta版本。Flutter是Google用以幫助開發者在Ios和Android兩個平臺開發高質量原生應用的全新移動U

從零開始搭建Electron+Vue+Webpack專案框架,程式碼,同時構建客戶端、web端(

摘要:隨著前端技術的飛速發展,越來越多的技術領域開始被前端工程師踏足。從NodeJs問世至今,各種前端工具腳手架、服務端框架層出不窮,“全棧工程師”對於前端開發者來說,再也不只是說說而已。在NodeJs及其衍生技術高速發展的同時,Nw和Electron的問世,更是為前端發展提速不少,依稀記得哪位前輩說過,“能

【原創】從零開始搭建Electron+Vue+Webpack專案框架,程式碼,同時構建客戶端、web端(二)

導航: (一)Electron跑起來(二)從零搭建Vue全家桶+webpack專案框架(三)Electron+Vue+Webpack,聯合除錯整個專案(未完待續)(四)Electron配置潤色(未完待續)(五)預載入及自動更新(未完待續)(六)構建、釋出整個專案(包括client和web)(未完待續) 摘要:

微信程式web-view元件

小程式web-view元件   不久前微信小程式釋出了web-view元件,這個訊息在各個圈裡引起不小的漣漪。近期正好在做小程式的專案,便研究了一下這個讓大家充滿期待的元件。   1,web-view這個元件是什麼鬼? 官網的介紹:web-view 元件是一個

微信程式web-view例項

微信小程式web-view例項 index.js //index.js //獲取應用例項 const app = getApp() Page({ /** * 頁面的初始資料 */ data: { },

wechat-程式web-view與網頁互動

wechat-小程式web-view與網頁互動. 官方api說明文件: https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html 前置物料 啟動好一個網

程式web-view使用

微信 web-view的使用: <web-view src="https://www.xxxxxxxxx.com/index.html" /> 從h5頁面跳到小程式的其他頁面: <!-- html程式碼中引入JS SDK --> <script t