成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【Vue原理】Compile - 源碼版 之 Parse 主要流程

Forest10 / 1995人閱讀

寫文章不容易,點個贊唄兄弟  


專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧
研究基于 Vue版本 【2.5.17】

如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關(guān)注公眾號也可以吧

【Vue原理】Compile - 源碼版 之 Parse 主要流程

本文難度較繁瑣,需要耐心觀看,如果你對 compile 源碼暫時不感興趣可以先移步白話版 Compile - 白話版,

parse 是 渲染三巨頭的老大,其作用是把 template 字符串模板,轉(zhuǎn)換成 ast

其涉及源碼也是多得一批,達(dá)到了 一千多行,想想如果我把全部源碼放到文章里面來簡直不能看,所以我打算只保留主要部分,就是正常流程可以走通,去掉那些特殊處理的地方

大部分源碼都是特殊處理,比如 script ,style,input ,pre 等標(biāo)簽,這次全部都去掉,只留下通用元素的處理流程,留下一個骨架

因為 parse 的內(nèi)容非常的多,除了精簡源碼之外,我還通過不同內(nèi)容劃分文章去記錄

今天,要記錄的就是 parse 解析 template 成 ast 的大致流程,而怎么解析標(biāo)簽名,怎么解析標(biāo)簽屬性會暫時忽略,而獨立成文。當(dāng)有解析標(biāo)簽名和解析屬性的地方會直接出結(jié)果。比如當(dāng)我說在 模板 "

" 匹配出頭標(biāo)簽時,直接就得到 div ,而不會去考究是如何匹配出來的

好的,到底 template 是怎么變成 ast 的呢?跟著我去探索把~

AST

先來說說 ast 吧,這種復(fù)雜的概念,反正是需要查的。所以本文根本不需要解釋太多

直接說我的理解吧

抽象語法樹,以樹狀形式表現(xiàn)出語法結(jié)構(gòu)

直接使用例子去直觀感受就好了

111

用 ast 去描述這個模板就是

{ 
    tag:"div",    

    type :1 , 

    children:[ { 
        type:3, 
        text:"11" 
    } ] 
}

簡單得一批把,復(fù)雜的這里也不提了,反正跟 parse 沒多大關(guān)系我覺得

另外記一下,節(jié)點的 type 表示的意思

type:1,節(jié)點

type:2,表達(dá)式,比如 {{isShow}}

type:3,純文本

現(xiàn)在就開始 parse 的內(nèi)容了,那么就看 parse 的源碼

Parse

parse 是渲染三巨頭的老大,同時它也是一個函數(shù),源碼如下

function parse(template) {    



    var stack = []; // 緩存模板中解析的每個節(jié)點的 ast

    var root;   // 根節(jié)點,是 ast
    var currentParent; // 當(dāng)前解析的標(biāo)簽的父節(jié)點

    /**
    * parseHTML 處理 template 匹配標(biāo)簽,再傳入 start,end,chars 等方法
    **/
    parseHTML(template, {        

        start: (..被抽出,在后面)

         end: (..被抽出,在后面), // 為 起始標(biāo)簽 開啟閉合節(jié)點
         chars: (..被抽出,在后面) // 文字節(jié)點
    });    



    return root

}

parse 接收 template 字符串,使用 parseHTML 這個函數(shù)在 template 中匹配標(biāo)簽

并傳入 start,end,chars 三個函數(shù) 供 parseHTML 處理標(biāo)簽等內(nèi)容

start,end,chars 方法都已經(jīng)被我抽出來,放在后面逐個說明

下面來看下其中聲明的三個變量

1 stack

是一個數(shù)組存放模板中按順序 從頭到尾 每個標(biāo)簽的 ast

注:不會存放單標(biāo)簽的 ast ,比如 input,img 這些

比如 stack 是這樣的

stack=[{ 
    tag:"div",    

    type :1 , 

    children:[ { 
        type:3, 
        text:"11" 
    } ] 
}]

主要作用是幫助理清節(jié)點父子關(guān)系

2 root

每個模板都必須有一個根節(jié)點。寫過 Vue 項目的都知道了,所以一般解析到第一個標(biāo)簽的時候,會直接設(shè)置這個標(biāo)簽為 根節(jié)點

并且最后返回的也是 root

不可以存在兩個根節(jié)點(有 v-if 的不討論)

3 currentParent

在解析標(biāo)簽的時候,必須要知道這個標(biāo)簽的 父節(jié)點時誰

這樣才知道 這個標(biāo)簽是誰的子節(jié)點,才能把這個節(jié)點添加給相應(yīng)的 節(jié)點的 children

注:根節(jié)點 沒有 父節(jié)點,所以就是 undefined

parse 源碼已經(jīng)被我精簡得很簡單了,主要內(nèi)容其實就在 其中涉及的四個方法中

parseHTML,start,end,chars

parseHTML 是處理 template 的主力,其他三個函數(shù)是功能類型的,負(fù)責(zé)處理相應(yīng)的內(nèi)容。 例如,start 是處理頭標(biāo)簽的,end 是處理尾標(biāo)簽的,chars 是處理文本的

先來看看 parseHTML

處理 template

parseHTML 作為處理 template,匹配標(biāo)簽的函數(shù),是十分龐大的,其中兼顧了非常多情況的處理

而本次在不影響流程的情況下,我去掉了下面這些處理,優(yōu)化閱讀

1、沒有結(jié)束標(biāo)簽的處理

2、文字中包含 < 的處理

3、注釋的處理

4、忽略首尾空白字符,默認(rèn)起始和結(jié)尾都是標(biāo)簽

個人認(rèn)為主要內(nèi)容為三個

1、循環(huán) template 匹配標(biāo)簽

2、把匹配到的內(nèi)容,傳給相應(yīng)的方法處理

3、截斷 template

來看源碼,已經(jīng)簡化得不行了,但是還是要花點心思看看

function parseHTML(html, options) {    



    while (html) {       



         // 尋找 < 的起始位置

        var textEnd = html.indexOf("<"),
            text ,rest ,next;        



        // 模板起始位置是標(biāo)簽開頭 <

        if (textEnd === 0) {   

               

            /**
             * 如果是尾標(biāo)簽的 <
             * 比如 html = "
" , 匹配出 endTagMatch =["
", "div"] */ var endTagMatch = html.match(endTag); if (endTagMatch) { // endTagMatch[0]="" html = html.substring(endTagMatch[0].length); // 處理尾標(biāo)簽,方法后面有記錄 options.end(); continue } /** * 如果是起始標(biāo)簽的 < * parseStartTag 作用是,匹配標(biāo)簽存在的屬性,截斷 template * html = "
", * parseStartTag 處理之后,startTagMatch = {tagName: "div", attrs: []} */ var startTagMatch = parseStartTag(); // 匹配到 起始標(biāo)簽之后 if (startTagMatch) { // 處理起始標(biāo)簽,后面有介紹 options.start(起始標(biāo)簽的信息); continue } } // 模板起始位置不是 <,而是文字 if (textEnd >= 0) { text = html.substring(0, textEnd); html = html.substring(n); } // 處理文字,后面有介紹 if (options.chars && text) { options.chars(text); } } }

思路如下

1匹配 < 這個符號

因為他是標(biāo)簽的開頭(已經(jīng)排除了文字中含有 < 的處理,不做討論)

2如果 template 開頭是 <

那么可能是 尾標(biāo)簽,可能是 頭標(biāo)簽,那么就需要判斷到底是哪個

1、先匹配尾標(biāo)簽,如果匹配到,那么就是尾標(biāo)簽,使用 end 方法處理。

2、如果不是,使用 parseStartTag 函數(shù)匹配得到首標(biāo)簽,并把 首標(biāo)簽信息傳給 start 處理

parseStartTag 就是使用正則在template 中匹配出 首標(biāo)簽信息,其中包括標(biāo)簽名,屬性等

比如 template 是

html = "
111
;"

parseStartTag 處理匹配之后得到

{    

    tagName: "div", 

    attrs: [{name:"22"}]
}
3 如果 template 開頭不是 <

那么證明 開頭 到 < 的位置這一段,是字符串,那么就是文本了

傳給 chars 方法處理

每次處理一次,就會截斷到匹配的位置,然后 template 越來越短,直接為空,退出 while,于是處理完畢

對于截斷呢,使用 substring,可能忘了怎么作用的,寫個小例子

傳入數(shù)字,表示這個位置前面的字符串都不要

然后,就到了我們其他三個方法的閃亮登場了

處理頭標(biāo)簽

每當(dāng) parseHTML 匹配到一個 首標(biāo)簽,都會把該標(biāo)簽的信息傳給 start 方法,讓他來處理

function start(tag, attrs, unary) {    



    // 創(chuàng)建 AST 節(jié)點

    var element = createASTElement(tag, attrs, currentParent);      



    /**
     * ...省略了一段處理 vFor,vIf,解析 @ 等屬性指令的代碼
     **/

    // 設(shè)置根節(jié)點,一個模板只有一個根節(jié)點
    if (!root) root = element;    



    // 處理父子關(guān)系

    if (currentParent) {
        currentParent.children.push(element);
        element.parent = currentParent;
    }    



    // 不是單標(biāo)簽(input,img 那些),就需要保存 stack

    if (!unary) {
        currentParent = element;
        stack.push(element);
    }
}

精簡得一目了然(面目全非),看得極度舒適

看看 start 方法都做了哪些惡呢

1、創(chuàng)建 ast

2、解析 attrs,并存放到 ast (已省略屬性解析)

3、設(shè)置根節(jié)點,父節(jié)點,把節(jié)點添加進(jìn)父節(jié)點的 children

4、ast 保存進(jìn) stack

好像不用解釋太多,肯定都看得懂啊,除了一個 創(chuàng)建 ast 的函數(shù)

這就來源碼

function createASTElement(tag, attrs, parent) {    



    return {        

        type: 1,        

        tag: tag,        

        attrsList: attrs,        

        // 把 attrs 數(shù)組 轉(zhuǎn)成 對象

        attrsMap: makeAttrsMap(attrs),        

        parent: parent,        

        children: []

    }
}

創(chuàng)建一個 ast 結(jié)構(gòu),保存數(shù)據(jù)

直接返回一個對象,非常明了,包含的各種屬性,應(yīng)該也能看懂

其中有一個 makeAttrsMap 函數(shù),舉個栗子

模板上的屬性,經(jīng)過 parseHTML 解析成一個數(shù)組,如下

[{    

    name:"hoho" ,value:"333"

},{    

    name:"href" ,value:"444"

}]

makeAttrMap 轉(zhuǎn)成對象成這樣

{ hoho:"333",   href:"444"}

然后就保存在 ast 中

處理尾標(biāo)簽

每當(dāng) parseHTML 匹配到 尾標(biāo)簽 ,比如 "

" 的時候,就會調(diào)用傳入的 end 方法

來看看吧

function end() {    

    // 標(biāo)簽解析結(jié)束,移除該標(biāo)簽

    stack.length -= 1;
    currentParent = stack[stack.length - 1];
}

乍一看,很簡單?。∵@么少(都是精簡...)

作用有兩個

1從 stack 數(shù)組中移除這個節(jié)點

stack 保存的是匹配到的頭標(biāo)簽,如果標(biāo)簽已經(jīng)匹配結(jié)束了,那么就需要移除

stack 就是為了明確各節(jié)點間父子關(guān)系而存在的

保證 stack 中最后一個節(jié)點,永遠(yuǎn)是下次匹配的節(jié)點的父節(jié)點

舉個栗子,存在下面模板

stack 匹配兩個 頭標(biāo)簽之后

stack = [ "div" , "section"]

看看 start 可以知道,此時 currentParent = section

然后匹配到

,則移除 stack 中的 section,并且重設(shè) currentParent

stack = ["div"]

currentParent = "div"

再匹配到 p 的時候,p 的父節(jié)點就是 div,父子順序就是正確的了

2重新設(shè)置 stack 最后一個節(jié)點為父節(jié)點 處理文本字符串

當(dāng) parseHTML 去匹配 < 的時候,發(fā)現(xiàn) template 不是 <,template開頭 到 < 還有一段距離

那么這段距離的內(nèi)容就是 文本了,那么就會把這段文本傳給 chars 方法處理

來看看源碼

function chars(text) {    



    // 必須存在根節(jié)點,不可能用文字開頭

    if(!currentParent) return



    var children = currentParent.children;    



    // 通過 parseText 解析成字符串,判斷是否含有雙括號表達(dá)式,比如 {{item}}

    // 如果是有表達(dá)式,會存放多一些信息,
    var res = parseText(text)    



    if(res) {

        children.push({            

            type: 2,            

            expression: res.expression,            

            tokens: res.tokens,            

            text: text

        });
    }    



    // 普通字符串,直接存為 字符串子節(jié)點

    else if(
      !children.length ||
      children[children.length - 1].text !== " "
    ) {
        children.push({            

            type: 3,            

            text: text

        });
    }
}

這段代碼主要作用就是,為 父節(jié)點 添加 文本子節(jié)點

而文本子節(jié)點分為兩種類型

1、普通型,直接存為文本子節(jié)點

2、表達(dá)式型,需要經(jīng)過 parseText 處理

直接以結(jié)果來定義吧

比如處理這段文本

{{isShow}}

{    

    expression: toString(isShow)

    tokens: [{@binding: "isShow"}]
}

主要是為了把表達(dá)式 isShow 拿到,方便后面從實例上獲取值

好的,現(xiàn)在,template 處理流程所涉及的主要方法都講完了

現(xiàn)在用上面這些函數(shù)來走一個流程

現(xiàn)在有一個模板

11
1 開始循環(huán) tempalte

匹配到第一個 頭標(biāo)簽 (

),傳入 parse-start,生成 對應(yīng)的 ast

該 div 的 ast 變成根節(jié)點 root,并設(shè)置其為當(dāng)前父節(jié)點 currentParent,保存進(jìn)節(jié)點緩存數(shù)組 stack

此時

stack = [ { tag:"div" , children:[ ] } ]

第一輪處理結(jié)束,template 截斷到第一次匹配到的位置

此時,template = 11

2 開始第二次遍歷

開始匹配 <,發(fā)現(xiàn) < 不在開頭,而 開頭位置 到 < 有一段普通字符串

調(diào)用 parse-char,傳入字符串

發(fā)現(xiàn)其沒有 雙括號表達(dá)式,直接給父節(jié)點添加簡單子節(jié)點

currentParent.children.push({ type:3 , text:"11" })

此時

stack =[ { tag:"div" , children:[ { type:3 , text:"11" } ] } ]

第二輪處理結(jié)束,template 截斷到剛剛匹配完的字符串

此時,template =

3 開始第三輪遍歷

繼續(xù)尋找 <,發(fā)現(xiàn)就在開頭,但是這是一個結(jié)束標(biāo)簽,標(biāo)簽名是 div

因為 stack 是節(jié)點順序存入的,這個結(jié)束標(biāo)簽肯定屬于 stack 最后一個 標(biāo)簽

由于 該標(biāo)簽匹配完畢,所以從 stack 中移除

并且設(shè)置 當(dāng)前父節(jié)點 currentParent 為 stack 倒數(shù)第二個

第三次遍歷結(jié)束,template 繼續(xù)截斷

此時 template 為空了,結(jié)束所有遍歷

返回此次 tempalte 解析的 root

{ 
    tag:"div",type :1 , 
    children:[ { type:3 , text:"11" } ] 
}

于是 parse 就成功把 tempalte 解析成了 ast ,就是 root

總結(jié)

本問講的是 parse 的主要流程,忽略了內(nèi)部的處理細(xì)節(jié),比如怎么解析標(biāo)簽,怎么解析屬性,其他內(nèi)容都會獨立成文章

在 parse 的流程中,大致有五個函數(shù),我們屢一下,如下

parse,parseHTML,start,end,chars

parse 是整個 parse 流程的總函數(shù)

parseHTML 是 parse 處理的主力函數(shù)

start,end,chars 是 在 parse 中傳給 parseHTML ,用來幫助處理 匹配的標(biāo)簽信息的函數(shù),這三個函數(shù)會在 parseHTML 中被調(diào)用

最后

鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當(dāng)?shù)牡胤剑瑲g迎后臺聯(lián)系本人,有重謝

GPU云服務(wù)器 云服務(wù)器 工作流的主要原理 vue源碼 vue webrtc 源碼 源碼中文說明之

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/106593.html

相關(guān)文章

發(fā)表評論

0條評論

Forest10

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<