国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

Forest10 / 1977人閱讀

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


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

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

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

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

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

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

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

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

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

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

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

AST

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

直接說我的理解吧

抽象語法樹,以樹狀形式表現出語法結構

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

111

用 ast 去描述這個模板就是

{ 
    tag:"div",    

    type :1 , 

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

簡單得一批把,復雜的這里也不提了,反正跟 parse 沒多大關系我覺得

另外記一下,節點的 type 表示的意思

type:1,節點

type:2,表達式,比如 {{isShow}}

type:3,純文本

現在就開始 parse 的內容了,那么就看 parse 的源碼

Parse

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

function parse(template) {    



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

    var root;   // 根節點,是 ast
    var currentParent; // 當前解析的標簽的父節點

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

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

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



    return root

}

parse 接收 template 字符串,使用 parseHTML 這個函數在 template 中匹配標簽

并傳入 start,end,chars 三個函數 供 parseHTML 處理標簽等內容

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

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

1 stack

是一個數組存放模板中按順序 從頭到尾 每個標簽的 ast

注:不會存放單標簽的 ast ,比如 input,img 這些

比如 stack 是這樣的

stack=[{ 
    tag:"div",    

    type :1 , 

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

主要作用是幫助理清節點父子關系

2 root

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

并且最后返回的也是 root

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

3 currentParent

在解析標簽的時候,必須要知道這個標簽的 父節點時誰

這樣才知道 這個標簽是誰的子節點,才能把這個節點添加給相應的 節點的 children

注:根節點 沒有 父節點,所以就是 undefined

parse 源碼已經被我精簡得很簡單了,主要內容其實就在 其中涉及的四個方法中

parseHTML,start,end,chars

parseHTML 是處理 template 的主力,其他三個函數是功能類型的,負責處理相應的內容。 例如,start 是處理頭標簽的,end 是處理尾標簽的,chars 是處理文本的

先來看看 parseHTML

處理 template

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

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

1、沒有結束標簽的處理

2、文字中包含 < 的處理

3、注釋的處理

4、忽略首尾空白字符,默認起始和結尾都是標簽

個人認為主要內容為三個

1、循環 template 匹配標簽

2、把匹配到的內容,傳給相應的方法處理

3、截斷 template

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

function parseHTML(html, options) {    



    while (html) {       



         // 尋找 < 的起始位置

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



        // 模板起始位置是標簽開頭 <

        if (textEnd === 0) {   

               

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

思路如下

1匹配 < 這個符號

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

2如果 template 開頭是 <

那么可能是 尾標簽,可能是 頭標簽,那么就需要判斷到底是哪個

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

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

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

比如 template 是

html = "
111
;"

parseStartTag 處理匹配之后得到

{    

    tagName: "div", 

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

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

傳給 chars 方法處理

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

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

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

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

處理頭標簽

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

function start(tag, attrs, unary) {    



    // 創建 AST 節點

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



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

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



    // 處理父子關系

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



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

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

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

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

1、創建 ast

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

3、設置根節點,父節點,把節點添加進父節點的 children

4、ast 保存進 stack

好像不用解釋太多,肯定都看得懂啊,除了一個 創建 ast 的函數

這就來源碼

function createASTElement(tag, attrs, parent) {    



    return {        

        type: 1,        

        tag: tag,        

        attrsList: attrs,        

        // 把 attrs 數組 轉成 對象

        attrsMap: makeAttrsMap(attrs),        

        parent: parent,        

        children: []

    }
}

創建一個 ast 結構,保存數據

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

其中有一個 makeAttrsMap 函數,舉個栗子

模板上的屬性,經過 parseHTML 解析成一個數組,如下

[{    

    name:"hoho" ,value:"333"

},{    

    name:"href" ,value:"444"

}]

makeAttrMap 轉成對象成這樣

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

然后就保存在 ast 中

處理尾標簽

每當 parseHTML 匹配到 尾標簽 ,比如 "

" 的時候,就會調用傳入的 end 方法

來看看吧

function end() {    

    // 標簽解析結束,移除該標簽

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

乍一看,很簡單??!這么少(都是精簡...)

作用有兩個

1從 stack 數組中移除這個節點

stack 保存的是匹配到的頭標簽,如果標簽已經匹配結束了,那么就需要移除

stack 就是為了明確各節點間父子關系而存在的

保證 stack 中最后一個節點,永遠是下次匹配的節點的父節點

舉個栗子,存在下面模板

stack 匹配兩個 頭標簽之后

stack = [ "div" , "section"]

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

然后匹配到

,則移除 stack 中的 section,并且重設 currentParent

stack = ["div"]

currentParent = "div"

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

2重新設置 stack 最后一個節點為父節點 處理文本字符串

當 parseHTML 去匹配 < 的時候,發現 template 不是 <,template開頭 到 < 還有一段距離

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

來看看源碼

function chars(text) {    



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

    if(!currentParent) return



    var children = currentParent.children;    



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

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



    if(res) {

        children.push({            

            type: 2,            

            expression: res.expression,            

            tokens: res.tokens,            

            text: text

        });
    }    



    // 普通字符串,直接存為 字符串子節點

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

            type: 3,            

            text: text

        });
    }
}

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

而文本子節點分為兩種類型

1、普通型,直接存為文本子節點

2、表達式型,需要經過 parseText 處理

直接以結果來定義吧

比如處理這段文本

{{isShow}}

{    

    expression: toString(isShow)

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

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

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

現在用上面這些函數來走一個流程

現在有一個模板

11
1 開始循環 tempalte

匹配到第一個 頭標簽 (

),傳入 parse-start,生成 對應的 ast

該 div 的 ast 變成根節點 root,并設置其為當前父節點 currentParent,保存進節點緩存數組 stack

此時

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

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

此時,template = 11

2 開始第二次遍歷

開始匹配 <,發現 < 不在開頭,而 開頭位置 到 < 有一段普通字符串

調用 parse-char,傳入字符串

發現其沒有 雙括號表達式,直接給父節點添加簡單子節點

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

此時

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

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

此時,template =

3 開始第三輪遍歷

繼續尋找 <,發現就在開頭,但是這是一個結束標簽,標簽名是 div

因為 stack 是節點順序存入的,這個結束標簽肯定屬于 stack 最后一個 標簽

由于 該標簽匹配完畢,所以從 stack 中移除

并且設置 當前父節點 currentParent 為 stack 倒數第二個

第三次遍歷結束,template 繼續截斷

此時 template 為空了,結束所有遍歷

返回此次 tempalte 解析的 root

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

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

總結

本問講的是 parse 的主要流程,忽略了內部的處理細節,比如怎么解析標簽,怎么解析屬性,其他內容都會獨立成文章

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

parse,parseHTML,start,end,chars

parse 是整個 parse 流程的總函數

parseHTML 是 parse 處理的主力函數

start,end,chars 是 在 parse 中傳給 parseHTML ,用來幫助處理 匹配的標簽信息的函數,這三個函數會在 parseHTML 中被調用

最后

鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當的地方,歡迎后臺聯系本人,有重謝

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106593.html

相關文章

發表評論

0條評論

Forest10

|高級講師

TA的文章

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