摘要:下面用具體代碼進(jìn)行分析。匹配不到那么就是開(kāi)始標(biāo)簽,調(diào)用函數(shù)解析。如這里的轉(zhuǎn)化為加上是為了的下一步轉(zhuǎn)為函數(shù),本文中暫時(shí)不會(huì)用到。再把轉(zhuǎn)化后的內(nèi)容進(jìn)。
什么是AST
在Vue的mount過(guò)程中,template會(huì)被編譯成AST語(yǔ)法樹(shù),AST是指抽象語(yǔ)法樹(shù)(abstract syntax tree或者縮寫(xiě)為AST),或者語(yǔ)法樹(shù)(syntax tree),是源代碼的抽象語(yǔ)法結(jié)構(gòu)的樹(shù)狀表現(xiàn)形式。
Virtual DomVue的一個(gè)厲害之處就是利用Virtual DOM模擬DOM對(duì)象樹(shù)來(lái)優(yōu)化DOM操作的一種技術(shù)或思路。
Vue源碼中虛擬DOM構(gòu)建經(jīng)歷 template編譯成AST語(yǔ)法樹(shù) -> 再轉(zhuǎn)換為render函數(shù) 最終返回一個(gè)VNode(VNode就是Vue的虛擬DOM節(jié)點(diǎn))
本文通過(guò)對(duì)源碼中AST轉(zhuǎn)化部分進(jìn)行簡(jiǎn)單提取,因?yàn)樵创a中轉(zhuǎn)化過(guò)程還需要進(jìn)行各種兼容判斷,非常復(fù)雜,所以筆者對(duì)主要功能代碼進(jìn)行提取,用了300-400行代碼完成對(duì)template轉(zhuǎn)化為AST這個(gè)功能。下面用具體代碼進(jìn)行分析。
function parse(template) { var currentParent; //當(dāng)前父節(jié)點(diǎn) var root; //最終返回出去的AST樹(shù)根節(jié)點(diǎn) var stack = []; parseHTML(template, { start: function start(tag, attrs, unary) { ...... }, end: function end() { ...... }, chars: function chars(text) { ...... } }) return root }
第一步就是調(diào)用parse這個(gè)方法,把template傳進(jìn)來(lái),這里假設(shè)template為
然后聲明3個(gè)變量
currentParent -> 存放當(dāng)前父元素,root -> 最終返回出去的AST樹(shù)根節(jié)點(diǎn),stack -> 一個(gè)棧用來(lái)輔助樹(shù)的建立
接著調(diào)用parseHTML函數(shù)進(jìn)行轉(zhuǎn)化,傳入template和options(包含3個(gè)方法 start,end,chars 等下用到這3個(gè)函數(shù)再進(jìn)行解釋)接下來(lái)先看parseHTML這個(gè)方法
function parseHTML(html, options) { var stack = []; //這里和上面的parse函數(shù)一樣用到stack這個(gè)數(shù)組 不過(guò)這里的stack只是為了簡(jiǎn)單存放標(biāo)簽名 為了和結(jié)束標(biāo)簽進(jìn)行匹配的作用 var isUnaryTag$$1 = isUnaryTag; //判斷是否為自閉合標(biāo)簽 var index = 0; var last; while (html) { // 第一次進(jìn)入while循環(huán)時(shí),由于字符串以<開(kāi)頭,所以進(jìn)入startTag條件,并進(jìn)行AST轉(zhuǎn)換,最后將對(duì)象彈入stack數(shù)組中 last = html; var textEnd = html.indexOf("<"); if (textEnd === 0) { // 此時(shí)字符串是不是以<開(kāi)頭 // End tag: var endTagMatch = html.match(endTag); if (endTagMatch) { var curIndex = index; advance(endTagMatch[0].length); parseEndTag(endTagMatch[1], curIndex, index); continue } // Start tag: // 匹配起始標(biāo)簽 var startTagMatch = parseStartTag(); //處理后得到match if (startTagMatch) { handleStartTag(startTagMatch); continue } } // 初始化為undefined 這樣安全且字符數(shù)少一點(diǎn) var text = (void 0), rest = (void 0), next = (void 0); if (textEnd >= 0) { // 截取<字符索引 =>
函數(shù)進(jìn)入while循環(huán)對(duì)html進(jìn)行獲取<標(biāo)簽索引 var textEnd = html.indexOf("<");如果textEnd === 0 說(shuō)明當(dāng)前是標(biāo)簽
function parseStartTag() { //返回匹配對(duì)象 var start = html.match(startTagOpen); // 正則匹配 if (start) { var match = { tagName: start[1], // 標(biāo)簽名(div) attrs: [], // 屬性 start: index // 游標(biāo)索引(初始為0) }; advance(start[0].length); var end, attr; while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { advance(attr[0].length); match.attrs.push(attr); } if (end) { advance(end[0].length); // 標(biāo)記結(jié)束位置 match.end = index; //這里的index 是在 parseHTML就定義 在advance里面相加 return match // 返回匹配對(duì)象 起始位置 結(jié)束位置 tagName attrs } } }
該函數(shù)主要是為了構(gòu)建一個(gè)match對(duì)象,對(duì)象里面包含tagName(標(biāo)簽名),attrs(標(biāo)簽的屬性),start(<左開(kāi)始標(biāo)簽在template中的位置),end(>右開(kāi)始標(biāo)簽在template中的位置) 如template =
start:0 end:14 如圖:
接著把match返回出去 作為調(diào)用handleStartTag的參數(shù)
var startTagMatch = parseStartTag(); //處理后得到match if (startTagMatch) { handleStartTag(startTagMatch); continue }
接下來(lái)看handleStartTag這個(gè)函數(shù):
function handleStartTag(match) { var tagName = match.tagName; var unary = isUnaryTag$$1(tagName) //判斷是否為閉合標(biāo)簽 var l = match.attrs.length; var attrs = new Array(l); for (var i = 0; i < l; i++) { var args = match.attrs[i]; var value = args[3] || args[4] || args[5] || ""; attrs[i] = { name: args[1], value: value }; } if (!unary) { stack.push({tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs}); lastTag = tagName; } if (options.start) { options.start(tagName, attrs, unary, match.start, match.end); } }
函數(shù)中分為3部分 第一部分是for循環(huán)是對(duì)attrs進(jìn)行轉(zhuǎn)化,我們從上一步的parseStartTag()得到的match對(duì)象中的attrs屬性如圖
當(dāng)時(shí)attrs是上面圖這樣子滴 我們通過(guò)這個(gè)循環(huán)把它轉(zhuǎn)化為只帶name 和 value這2個(gè)屬性的對(duì)象 如圖:
接著判斷如果不是自閉合標(biāo)簽,把標(biāo)簽名和屬性推入棧中(注意 這里的stack這個(gè)變量在parseHTML中定義,作用是為了存放標(biāo)簽名 為了和結(jié)束標(biāo)簽進(jìn)行匹配的作用。)接著調(diào)用最后一步 options.start 這里的options就是我們?cè)趐arse函數(shù)中 調(diào)用parseHTML是傳進(jìn)來(lái)第二個(gè)參數(shù)的那個(gè)對(duì)象(包含start end chars 3個(gè)方法函數(shù)) 這里開(kāi)始看options.start這個(gè)函數(shù)的作用:
start: function start(tag, attrs, unary) { var element = { type: 1, tag: tag, attrsList: attrs, attrsMap: makeAttrsMap(attrs), parent: currentParent, children: [] }; processAttrs(element); if (!root) { root = element; } if(currentParent){ currentParent.children.push(element); element.parent = currentParent; } if (!unary) { currentParent = element; stack.push(element); } }
這個(gè)函數(shù)中 生成element對(duì)象 再連接元素的parent 和 children節(jié)點(diǎn) 最終push到棧中
此時(shí)棧中第一個(gè)元素生成 如圖:
完成了while循環(huán)的第一次執(zhí)行,進(jìn)入第二次循環(huán)執(zhí)行,這個(gè)時(shí)候html變成{{message}}
接著繼續(xù)執(zhí)行第三個(gè)循環(huán) 這個(gè)時(shí)候是處理文本節(jié)點(diǎn)了 {{message}}
// 初始化為undefined 這樣安全且字符數(shù)少一點(diǎn) var text = (void 0), rest = (void 0), next = (void 0); if (textEnd >= 0) { // 截取<字符索引 =>
這里的作用就是把文本提取出來(lái) 調(diào)用options.chars這個(gè)函數(shù) 接下來(lái)看options.chars
chars: function chars(text) { if (!currentParent) { //如果沒(méi)有父元素 只是文本 return } var children = currentParent.children; //取出children // text => {{message}} if (text) { var expression; if (text !== " " && (expression = parseText(text))) { // 將解析后的text存進(jìn)children數(shù)組 children.push({ type: 2, expression: expression, text: text }); } else if (text !== " " || !children.length || children[children.length - 1].text !== " ") { children.push({ type: 3, text: text }); } } } })
這里的主要功能是判斷文本是{{xxx}}還是簡(jiǎn)單的文本xxx,如果是簡(jiǎn)單的文本 push進(jìn)父元素的children里面,type設(shè)置為3,如果是字符模板{{xxx}},調(diào)用parseText轉(zhuǎn)化。如這里的{{message}}轉(zhuǎn)化為 _s(message)(加上_s是為了AST的下一步轉(zhuǎn)為render函數(shù),本文中暫時(shí)不會(huì)用到。) 再把轉(zhuǎn)化后的內(nèi)容push進(jìn)children。
又走完一個(gè)循環(huán)了,這個(gè)時(shí)候html =
var endTagMatch = html.match(endTag); if (endTagMatch) { var curIndex = index; advance(endTagMatch[0].length); parseEndTag(endTagMatch[1], curIndex, index); continue }
接下來(lái)看parseEndTag這個(gè)函數(shù) 傳進(jìn)來(lái)了標(biāo)簽名 開(kāi)始索引和結(jié)束索引
function parseEndTag(tagName, start, end) { var pos, lowerCasedTagName; if (tagName) { lowerCasedTagName = tagName.toLowerCase(); } // Find the closest opened tag of the same type if (tagName) { // 獲取最近的匹配標(biāo)簽 for (pos = stack.length - 1; pos >= 0; pos--) { // 提示沒(méi)有匹配的標(biāo)簽 if (stack[pos].lowerCasedTag === lowerCasedTagName) { break } } } else { // If no tag name is provided, clean shop pos = 0; } if (pos >= 0) { // Close all the open elements, up the stack for (var i = stack.length - 1; i >= pos; i--) { if (options.end) { options.end(stack[i].tag, start, end); } } // Remove the open elements from the stack stack.length = pos; lastTag = pos && stack[pos - 1].tag; }
這里首先找到棧中對(duì)應(yīng)的開(kāi)始標(biāo)簽的索引pos,再?gòu)脑撍饕_(kāi)始到棧頂?shù)乃栽卣{(diào)用options.end這個(gè)函數(shù)
end: function end() { // pop stack stack.length -= 1; currentParent = stack[stack.length - 1]; },
把棧頂元素出棧,因?yàn)檫@個(gè)元素已經(jīng)匹配到結(jié)束標(biāo)簽了,再把當(dāng)前父元素更改。終于走完了,把html的內(nèi)容循環(huán)完,最終return root 這個(gè)root就是我們所要得到的AST
這只是Vue的冰山一角,文中有什么不對(duì)的地方請(qǐng)大家?guī)兔χ刚救俗罱惨恢痹趯W(xué)習(xí)Vue的源碼,希望能夠拿出來(lái)與大家一起分享經(jīng)驗(yàn),接下來(lái)會(huì)繼續(xù)更新后續(xù)的源碼,如果覺(jué)得有幫忙請(qǐng)給個(gè)Star哈
github地址為:https://github.com/zwStar/vue... 歡迎各位star或issues
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/89019.html
摘要:注意看注釋很粗很簡(jiǎn)單,我就是一程序員姓名,年齡,請(qǐng)聯(lián)系我吧是否保留注釋定義分隔符,默認(rèn)為對(duì)于轉(zhuǎn)成,則需要先獲取,對(duì)于這部分內(nèi)容,做一個(gè)簡(jiǎn)單的分析,具體的請(qǐng)自行查看源碼。其中的負(fù)責(zé)修改以及截取剩余模板字符串。 通過(guò)查看vue源碼,可以知道Vue源碼中使用了虛擬DOM(Virtual Dom),虛擬DOM構(gòu)建經(jīng)歷 template編譯成AST語(yǔ)法樹(shù) -> 再轉(zhuǎn)換為render函數(shù) 最終返回...
摘要:頁(yè)面這個(gè)實(shí)例,按理就需要解析兩次,但是有緩存之后就不會(huì)理清思路也就是說(shuō),其實(shí)內(nèi)核就是不過(guò)是經(jīng)過(guò)了兩波包裝的第一波包裝在中的內(nèi)部函數(shù)中內(nèi)部函數(shù)的作用是合并公共和自定義,但是相關(guān)代碼已經(jīng)省略,另一個(gè)就是執(zhí)行第二波包裝在中,目的是進(jìn)行緩存 寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 ...
摘要:今年的月日,的版本正式發(fā)布了,其中核心代碼都進(jìn)行了重寫(xiě),于是就專門(mén)花時(shí)間,對(duì)的源碼進(jìn)行了學(xué)習(xí)。本篇文章就是源碼學(xué)習(xí)的總結(jié)。實(shí)現(xiàn)了并且將靜態(tài)子樹(shù)進(jìn)行了提取,減少界面重繪時(shí)的對(duì)比。的最新源碼可以去獲得。 Vue2.0介紹 從去年9月份了解到Vue后,就被他簡(jiǎn)潔的API所吸引。1.0版本正式發(fā)布后,就在業(yè)務(wù)中開(kāi)始使用,將原先jQuery的功能逐步的進(jìn)行遷移。 今年的10月1日,Vue的2...
寫(xiě)文章不容易,點(diǎn)個(gè)贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺(jué)得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也可以吧 【Vue原理】Compile - 源碼版 之 Parse 主要流程 本文難度較繁瑣,需要耐心觀看,如果你對(duì) compile 源碼暫時(shí)...
摘要:具體可以查看抽象語(yǔ)法樹(shù)。而則是帶緩存的編譯器,同時(shí)以及函數(shù)會(huì)被轉(zhuǎn)換成對(duì)象。會(huì)用正則等方式解析模板中的指令等數(shù)據(jù),形成語(yǔ)法樹(shù)。是將語(yǔ)法樹(shù)轉(zhuǎn)化成字符串的過(guò)程,得到結(jié)果是的字符串以及字符串。里面的節(jié)點(diǎn)與父節(jié)點(diǎn)的結(jié)構(gòu)類似,層層往下形成一棵語(yǔ)法樹(shù)。 寫(xiě)在前面 因?yàn)閷?duì)Vue.js很感興趣,而且平時(shí)工作的技術(shù)棧也是Vue.js,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue.js源碼,并做了總結(jié)與輸出。 文...
閱讀 1395·2021-11-04 16:11
閱讀 3059·2021-10-12 10:11
閱讀 2991·2021-09-29 09:47
閱讀 1627·2021-09-22 15:40
閱讀 1025·2019-08-29 15:43
閱讀 2814·2019-08-29 13:50
閱讀 1591·2019-08-29 13:28
閱讀 2698·2019-08-29 12:54