【一套程式碼小程式&Native&Web階段總結篇】可以這樣閱讀Vue原始碼
前言
在實際程式碼過程中我們發現,我們可能又要做H5站又要做小程式同時還要做個APP,這裡會造成很大的資源浪費,如果設定一個規則,讓我們可以先寫H5程式碼,然後將小程式以及APP的業務差異程式碼做掉,豈不快哉?但小程式的web框架並不開源,不然也用不著我們在此費力了,經過研究,小程式web端框架是一套自研的MVVM框架,於是我們馬上想到了藉助第三方框架:
經過簡單的研究,我們發現無論React或者Vue都是可以一定程度適應小程式開發模式的,市面上也有了對應的框架:mpvue&wepy
在使用以上任何一套框架體系前,我們都需要對MVVM框架有最基礎的瞭解,不然後面隨便遇到點什麼問題可能都會變得難以繼續,負責人要對團隊負責而不是單純只是想技術嚐鮮,所以生產專案一定要使用自己能完成hold住的技術
這段時間我們嘗試著去閱讀Vue的原始碼,但現在的Vue是一個工程化產物,我們要學習的核心程式碼可能不到其中的1/3,多出來的是各種特性以及細節處理,在不清楚程式碼意圖(目標)的情況下,事實上很難搞懂這段程式碼是要幹什麼,很多程式碼的出現都是一些精妙的小優化,知道的就會覺得十分驚豔,不知道的就會一頭霧水,一來就看Vue的原始碼反而不利於深入瞭解
出於這個原因在網上看了很多原始碼介紹的文章,出來就放一個Vue官方的流程圖,然後一套組合模組套路,基本就把我給打暈了,查詢了很多資料,還是發現一些寫的比較清晰的(我感覺適合多數人的)文章:
這兩篇文章都有一個主旨:
在沒有相關框架經驗的情況下,單單靠單步除錯以及網上的原始碼介紹,想要讀懂Vue原始碼是不太靠譜的做法,比較好的做法是自己照著Vue的原始碼寫一套簡單的,最基礎的MVVM框架,在完成這個框架後再去閱讀Vue或者React的程式碼要輕易的多,我這邊是非常認可這個說法的,所以我們照著fastCreateor的程式碼(他應該是參考的Vue)也擼了一個:
這裡與其說擼了一個MVVM框架,不如說給fastCreateor的程式碼加上了自我理解的註釋,通過這個過程也對MVVM框架有了第一步的認識,之前的文章或者程式碼都有些散,這裡將前面學習的內容再做一次彙總,多加一些圖示,幫助自己也幫助讀者更好的瞭解,於是讓我們開始吧!
PS:下面說的MVVM框架,基本就是Vue框架,並且是自己的理解,有問題請大家拍磚
MVVM框架的流程
我們梳理了MVVM框架的基本流程,這裡只看首次渲染的話:
① 解析html模板形成mvvm例項物件element(例項上的$node屬性) ② 處理element屬性,這裡包括屬性處理、事件處理、指令處理 ③ 使用處理過的element物件,為每個例項建立render方法 PS:new MVVM只會產生一個例項,每個html標籤都會形成一個vnode,元件會形成獨立的例項,與根例項以$parent與$children維護關係 ④ 使用render方法建立虛擬dom vnode,vm例項element已經具備所有建立虛擬dom的必要條件,render只是利用他們,如果程式碼組織得好,不使用render也行 ⑤ render執行後會生成虛擬dom vnode,藉助另一個神器snabbdom開始對比新舊虛擬dom的結構,完成最終渲染 PS:render執行時作用域在mvvm例項(vm)下
所以整個程式碼核心全部是圍繞著HTML=>element($node中間項,橋樑)=>render函式(執行返回vnode)=>引用snabbdom patch渲染
而抓住幾個點後,對應的幾個核心技術點也就出來了:
① 模板解析這裡對應著 HTMLParser,幫忙解決了很多問題
② 形成vnode需要的render函式,並且呼叫後維護彼此關係,這個是框架做的最多的工作
③ 生成真正的vnode,然後執行對比差異渲染patch操作,這塊重要的工作由snabbdom接手了
上面一行就是首次渲染執行的流程,下面幾個圖就是實現資料變化時候更新試圖的操作,分解到程式層面,核心就是:
① 例項化
② Parser => HTMLParser
③ codegen
在此基礎上再包裝出資料響應模型以及元件系統、指令系統,每個模組都很獨立,但又互相關聯,抓住這個主幹看各個分支這樣就會相對比較清晰。所以網上很多幾百行程式碼實現MVVM框架核心的就是隻做最核心這一塊,比如這個學習材料:https://github.com/DMQ/mvvm,非常簡單清晰,為了幫助更好的理解,我們這裡也寫了一段比較獨立的程式碼,包括了核心流程:
1 <div id="app"> 2 <input type="text" v-model="name"> 3 {{name}} 4 </div> 5 6 <script type="text/javascript" > 7 8 function getElById(id) { 9 return document.getElementById(id); 10 } 11 12 //主體物件,儲存所有的訂閱者 13 function Dep () { 14 this.subs = []; 15 } 16 17 //通知所有訂閱者資料變化 18 Dep.prototype.notify = function () { 19 for(let i = 0, l = this.subs.length; i < l; i++) { 20 this.subs[i].update(); 21 } 22 } 23 24 //新增訂閱者 25 Dep.prototype.addSub = function (sub) { 26 this.subs.push(sub); 27 } 28 29 let globalDataDep = new Dep(); 30 31 //觀察者,框架會接觸data的每一個與node相關的屬性, 32 //如果data沒有與任何節點產生關聯,則不予理睬 33 //實際的訂閱者物件 34 //注意,只要一個數據物件對應了一個node物件就會生成一個訂閱者,所以真實通知的時候應該需要做到通知到對應資料的dom,這裡不予關注 35 function Watcher(vm, node, name) { 36 this.name = name; 37 this.node = node; 38 this.vm = vm; 39 if(node.nodeType === 1) { 40 this.node.value = this.vm.data[name]; 41 } else if(node.nodeType === 3) { 42 this.node.nodeValue = this.vm.data[name] || ''; 43 } 44 globalDataDep.addSub(this); 45 46 } 47 48 Watcher.prototype.update = function () { 49 if(this.node.nodeType === 1) { 50 this.node.value = this.vm.data[this.name ]; 51 } else if(this.node.nodeType === 3) { 52 this.node.nodeValue = this.vm.data[this.name ] || ''; 53 } 54 } 55 56 //這塊程式碼僅做功能說明,不用當真 57 function compile(node, vm) { 58 let reg = /\{\{(.*)\}\}/; 59 60 //節點型別 61 if(node.nodeType === 1) { 62 let attrs = node.attributes; 63 //解析屬性 64 for(let i = 0, l = attrs.length; i < l; i++) { 65 if(attrs[i].nodeName === 'v-model') { 66 let name = attrs[i].nodeValue; 67 if(node.value === vm.data[name]) break; 68 69 // node.value = vm.data[name] || ''; 70 new Watcher(vm, node, name) 71 72 //此處不做太多判斷,直接繫結事件 73 node.addEventListener('input', function (e) { 74 //賦值操作 75 let newObj = {}; 76 newObj[name] = e.target.value; 77 vm.setData(newObj, true); 78 }); 79 80 break; 81 } 82 } 83 } else if(node.nodeType === 3) { 84 85 if(reg.test(node.nodeValue)) { 86 let name = RegExp.$1; // 獲取匹配到的name 87 name = name.trim(); 88 // node.nodeValue = vm.data[name] || ''; 89 new Watcher(vm, node, name) 90 } 91 } 92 } 93 94 //獲取節點 95 function nodeToFragment(node, vm) { 96 let flag = document.createDocumentFragment(); 97 let child; 98 99 while (child = node.firstChild) { 100 compile(child, vm); 101 flag.appendChild(child); 102 } 103 104 return flag; 105 } 106 107 function MVVM(options) { 108 this.data = options.data; 109 let el = getElById(options.el); 110 this.$dom = nodeToFragment(el, this) 111 this.$el = el.appendChild(this.$dom); 112 113 // this.$bindEvent(); 114 } 115 116 MVVM.prototype.setData = function (data, noNotify) { 117 for(let k in data) { 118 this.data[k] = data[k]; 119 } 120 //執行更新邏輯 121 // if(noNotify) return; 122 globalDataDep.notify(); 123 } 124 125 let mvvm = new MVVM({ 126 el: 'app', 127 data: { 128 name: '葉小釵' 129 } 130 }) 131 132 setTimeout(function() { 133 mvvm.setData({name: '刀狂劍痴葉小釵'}) 134 }, 3000) 135 136 </script>最簡單的mvvm例子
大家對照著這個例子自己擼一下,其中有幾個在業務中不太常用的知識點,第一個就是訪問器屬性,這裡大概寫個例子介紹下:
var obj = { }; // 為obj定義一個名為 name 的訪問器屬性 Object.defineProperty(obj, "name", { get: function () { console.log('get', arguments); }, set: function (val) { console.log('set', arguments); } }) obj.name = '葉小釵' console.log(obj, obj.name) /* set Arguments ["葉小釵", callee: ƒ, Symbol(Symbol.iterator): ƒ] get Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ] */
接下來我們對MVVM框架會用到的兩大神器依次做下介紹
神器HTMLPaser
這個庫完成的功能比較簡單,就是解析你傳入的html模板,這裡舉個例子:
1 <!doctype html> 2 <html> 3 <head> 4 <title>起步</title> 5 </head> 6 <body> 7 8 <div id="app"> 9 10 </div> 11 <script > 12 13 // Regular Expressions for parsing tags and attributes 14 let startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:@][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, 15 endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, 16 attr = /([a-zA-Z_:@][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g 17 18 // Empty Elements - HTML 5 19 let empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr") 20 21 // Block Elements - HTML 5 22 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") 23 24 // Inline Elements - HTML 5 25 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") 26 27 // Elements that you can, intentionally, leave open 28 // (and which close themselves) 29 let closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr") 30 31 // Attributes that have their values filled in disabled="disabled" 32 let fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected") 33 34 // Special Elements (can contain anything) 35 let special = makeMap("script,style") 36 37 function makeMap(str) { 38 var obj = {}, items = str.split(","); 39 for (var i = 0; i < items.length; i++) 40 obj[items[i]] = true; 41 return obj; 42 } 43 44 function HTMLParser(html, handler) { 45 var index, chars, match, stack = [], last = html; 46 stack.last = function () { 47 return this[this.length - 1]; 48 }; 49 50 while (html) { 51 chars = true; 52 53 // Make sure we're not in a script or style element 54 if (!stack.last() || !special[stack.last()]) { 55 56 // Comment 57 if (html.indexOf("<!--") == 0) { 58 index = html.indexOf("-->"); 59 60 if (index >= 0) { 61 if (handler.comment) 62 handler.comment(html.substring(4, index)); 63 html = html.substring(index + 3); 64 chars = false; 65 } 66 67 // end tag 68 } else if (html.indexOf("</") == 0) { 69 match = html.match(endTag); 70 71 if (match) { 72 html = html.substring(match[0].length); 73 match[0].replace(endTag, parseEndTag); 74 chars = false; 75 } 76 77 // start tag 78 } else if (html.indexOf("<") == 0) { 79 match = html.match(startTag); 80 81 if (match) { 82 html = html.substring(match[0].length); 83 match[0].replace(startTag, parseStartTag); 84 chars = false; 85 } 86 } 87 88 if (chars) { 89 index = html.indexOf("<"); 90 91 var text = index < 0 ? html : html.substring(0, index); 92 html = index < 0相關推薦
【一套程式碼小程式&Native&Web階段總結篇】可以這樣閱讀Vue原始碼
前言 在實際程式碼過程中我們發現,我們可能又要做H5站又要做小程式同時還要做個APP,這裡會造成很大的資源浪費,如果設定一個規則,讓我們可以先寫H5程式碼,然後將小程式以及APP的業務差異程式碼做掉,豈不快哉?但小程式的web框架並不開源,不然也用不著我們在此費力了,經過研究,小程式web端框架是一套自
一套程式碼小程式&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簡單調研
前言 最近工作比較忙,加之上個月生了小孩,小情人是各種折騰他爸媽,我們可以使用的獨立時間片不多,雖然這塊研究進展緩慢,但是一直做下去,肯定還是會有一些收穫的 之前我們這個課題研究一直是做獨立的研究,沒有去看已有的解決方案,這個是為了保證一個自己獨立的思維,無論獨立的思維還是人格都是很重要的東西,然獨學
【邢不行|量化小講堂系列27-數字貨幣篇】EOS期現套利,一週時間,15%無風險收益
引言: 邢不行的系列帖子“量化小講堂”,通過實際案例教初學者使用python進行量化投資,瞭解行業研究方向,希望能對大家有幫助。 【歷史文章彙總】請點選此處 個人微信:coinquant,有問題歡迎交流。 EOS期現套利,一週時間,15%無風險收益 在2
【基於微信小程式的社群電商平臺】需求分析心得——小豆芽
一、專案內容 基於微信小程式,做一個社群電商平臺,抓住社群電商的特點,做出特色,與微信整合,實現商品的個性化釋出,以及個性化營銷。 個性化釋出:使用者可以在應用上直接釋出自己的商品,通過搜尋心願單可以檢視當前買家使用者以及他們對商品的預期價格,在此便可建議賣家合理定價,尋求市場;作為買家,可以在當
【基於微信小程式的社群電商平臺】Alpha迭代心得
專案團隊:小豆芽 開發週期:11.5-12.2(Alpha版本) 設想和目標 1. 我們的軟體要解決什麼問題?是否定義得很清楚?是否對典型使用者和典型場景有清晰的描述? 解決問題:當前電商平臺賣家買家角色固化,買家在沒有尋求到心儀商品時無法記錄並告知他人,賣家出售商品無法確認是否有市場有需求;
手牽手,使用uni-app從零開發一款視訊小程式 (系列上 準備工作篇)
系列文章 手牽手,使用uni-app從零開發一款視訊小程式 (系列上 準備工作篇) 手牽手,使用uni-app從零開發一款視訊小程式 (系列下 開發實戰篇) 前言 好久不見,很久沒更新部落格了,前段時間在深圳出差,胡吃海喝頹廢了很久,不想每天下班刷抖音、打遊戲虛度光陰,準備把之前做的一個小程式案例詳細的介
手牽手,使用uni-app從零開發一款視訊小程式 (系列下 開發實戰篇)
系列文章 手牽手,使用uni-app從零開發一款視訊小程式 (系列上 準備工作篇) 手牽手,使用uni-app從零開發一款視訊小程式 (系列下 開發實戰篇) 掃碼體驗,先睹為快 可以掃描下微信小程式的二維碼,體驗一下開發完畢的效果: 程式碼地址: GitHub : https://github.co
【邢不行|量化小講堂系列25-數字貨幣篇】如何選擇數字貨幣交易所
引言: 邢不行的系列帖子“量化小講堂”,通過實際案例教初學者使用python進行量化投資,瞭解行業研究方向,希望能對大家有幫助。 【歷史文章彙總】請點選此處 個人微信:coinquant,有問題歡迎交流。 數字貨幣中的交易機會 加了我微信(coi
【邢不行|量化小講堂系列26-數字貨幣篇】OKEX交易所詳細介紹——小白從入門到精通
引言: 邢不行的系列帖子“量化小講堂”,通過實際案例教初學者使用python進行量化投資,瞭解行業研究方向,希望能對大家有幫助。 【歷史文章彙總】請點選此處 個人微信:coinquant,有問題歡迎交流。 本系列文章中用到的數字貨幣、A股資料可在www.y
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在跨端數量、擴充套件能力、效能體
【原創】從零開始搭建Electron+Vue+Webpack專案框架,一套程式碼,同時構建客戶端、web端(二)
導航: (一)Electron跑起來(二)從零搭建Vue全家桶+webpack專案框架(三)Electron+Vue+Webpack,聯合除錯整個專案(未完待續)(四)Electron配置潤色(未完待續)(五)預載入及自動更新(未完待續)(六)構建、釋出整個專案(包括client和web)(未完待續) 摘要:
React Native 中為IOS和Android設定不同的Style樣式,一套程式碼解決雙端顯示
React Native 開發中,大多數的元件都是IOS和Android通用的,包括大量的功能性程式碼,至少有80%以上的程式碼可以複用,而剩下的一些元件樣式/少量的程式碼會需要區分雙端,但是為了這少量的程式碼把IOS和Android完全區分這明顯不合適,程式碼複用性下降,程式碼維護量上升
從零開始搭建Electron+Vue+Webpack專案框架,一套程式碼,同時構建客戶端、web端(一)
摘要:隨著前端技術的飛速發展,越來越多的技術領域開始被前端工程師踏足。從NodeJs問世至今,各種前端工具腳手架、服務端框架層出不窮,“全棧工程師”對於前端開發者來說,再也不只是說說而已。在NodeJs及其衍生技術高速發展的同時,Nw和Electron的問世,更是為前端發展提速不少,依稀記得哪位前輩說過,“能