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

資訊專欄INFORMATION COLUMN

【Vue原理】Compile - 源碼版 之 Parse 標簽解析

loostudy / 1760人閱讀

摘要:當字符串開頭是時,可以匹配匹配尾標簽。從結尾,找到所在位置批量閉合。

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


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

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

【Vue原理】Compile - 源碼版 之 標簽解析

咳咳,上一篇文章,我們已經大致把 parse 的流程給記錄了一遍,如果沒看過,比較建議,先把這個流程給看了
Compile - 源碼版 之 Parse 主要流程

但是忽略了其中的處理細節,比如標簽怎么解析的,屬性怎么解析的,而且這兩個內容也是非常多的,所以需要多帶帶拎出來詳細記錄,不然混在一起,又臭又長

白話版在這~ Compile - 白話版

今天的內容是,記錄 標簽解析的 源碼

首先,開篇之前呢,我們來了解一下文章會出現過的正則

相關正則
var ncname = "[a-zA-Z_][w-.]*";

var qnameCapture = "((?:" + ncname + ":)?" + ncname + ")";

var startTagOpen = new RegExp(("^<" + qnameCapture));

var startTagClose = /^s*(/?)>/;

var endTag = new RegExp(("^]*>"));

var attribute = /^s*([^s""<>/=]+)(?:s*(=)s*(?:"([^"]*)"+|"([^"]*)"+|([^s""=<>`]+)))?/;

主要是四個

startTagOpen

匹配 頭標簽的 前半部分。當字符串開頭是 頭標簽時,可以匹配

startTagClose

匹配 頭標簽的 右尖括號。當字符串開頭是 > 時,可以匹配

endTag

匹配 尾標簽。當字符串開頭是 尾標簽時 可以匹配

attribute

匹配標簽上的屬性。當字符串開頭是屬性則可以匹配

好的,看完上面四個正則, 心里有個 * 數之后,相信下面的內容你會更加清晰些

下面的內容分為

1、循環遍歷 template

2、處理 頭標簽

3、處理 尾標簽

那么我們按一個個來說

循環遍歷template

通過上一篇內容,已經記錄過 是怎么循環遍歷 template 的了,就是通過 parseHTML 這個方法

這個方法,因為內容需要,也記錄一遍

首先,什么是循環遍歷template?

template 是一個字符串,所以每匹配完一個信息(比如頭標簽等),就會把template 截斷到匹配的結束位置

比如 template 是

"
1111
"

當我們匹配完了 頭標簽,那么 template 就會被截斷成

"1111
"

然后就這樣一直循環匹配新的 template,直到 template 被截斷成 空字符串,那么匹配完畢,其中跟截斷有關的一個重要函數就是 advance

這個函數在下面的源碼中用得非常多,需要牢記

其作用就是

1、截斷template

2、保存當前截斷的位置。比如你匹配了template到 字符串長度為5 的位置,那么 index 就是 4(從0開始)

function advance(n) {
    index += n;
    html = html.substring(n);
}

記住這個函數哦,我傳入一個數字 n,就是要把 template 從 n 截取到結尾

然后下面就看看簡化的 parseHTML 源碼(如果嫌長,先跳到分析)

function parseHTML(html, options) {    

    

    // 保存所有標簽的對象信息,tagName,attr,這樣,在解析尾部標簽的時候得到所屬的層級關系以及父標簽

    var stack = [];    

    var index = 0;    

    var last;    

    

    while (html) {



        last = html;        

        var textEnd = html.indexOf("<");        



        // 如果開頭是 標簽的 <

        if (textEnd === 0) {      

      

            /**
             * 如果開頭的 < 屬性尾標簽
             * 比如 html = "
" * 匹配出 endTagMatch =["
", "div"] */ * 如果開頭的 < 屬性尾標簽 * 比如 html = "
" * 匹配出 endTagMatch =["
", "div"] */ var endTagMatch = html.match(endTag); if (endTagMatch) { var curIndex = index; // endTagMatch[0]="" advance(endTagMatch[0].length); // endTagMatch[1]="div" parseEndTag(endTagMatch[1], curIndex, index); continue } /** * 如果開頭的 < 屬性 頭標簽 * parseStartTag 作用是,匹配標簽存在的屬性,截斷 template * html = "
" * startTagMatch = {tagName: "div", attrs: []} */ * 如果開頭的 < 屬性 頭標簽 * parseStartTag 作用是,匹配標簽存在的屬性,截斷 template * html = "
" * startTagMatch = {tagName: "div", attrs: []} */ var startTagMatch = parseStartTag(); if (startTagMatch) { handleStartTag(startTagMatch); continue } } var text ,rest ,next ; // 模板起始位置 不是 <,而是文字 if (textEnd >= 0) { text = html.substring(0, textEnd); advance(textEnd); } // 處理文字,上篇文章已經講過 if (options.chars && text) { options.chars(text); } } function parseStartTag(){...} function handleStartTag(){...} function parseEndTag(){...} }

這段代碼已經簡化得很簡單了,算是整體對 template 處理的一種把控我覺得

先匹配 < 的位置

1 如果 < 在template開頭

那么就是標簽(這里先不討論 字符串中的 <)

然后需要多一層判斷

如果是尾標簽的 <,那么交給 parseEndTag 處理

如果是頭標簽的 <,那么使用 handleStartTag 處理

2 如果 < 不在 template 開頭

那么表明 開頭到 < 的這段位置是字符串,但是本文內容是標簽解析,所以忽略這部分

然后每完成一次匹配,就需要調用 advace 去截斷 template

然后現在,我們假定有下面這段處理

template = "
111
" parseHTML(template)

匹配 < 在開頭,正則判斷之后,發現不是 尾標簽的 <,那么需要判斷是不是 頭標簽的

然后使用 parseStartTag 方法去匹配頭標簽信息

匹配成功,使用 handleStartTag 方法處理

看到在 parseHTML 末尾聲明了三個函數,為了避免太長,我挑了出來放在相應的內容講

而之所以會在里面聲明這個三個函數,是為了在這三個函數中,能訪問到 parseHTML 中的變量,比如 stack,index

處理頭標簽 parseStartTag

這個方法的作用就是

1、把頭標簽的所有信息集合起來,包括屬性,標簽名等

2、匹配完成之后同樣調用 advance 去截斷 template

3、把標簽信息 返回

源碼已經簡化,并且有做流程注釋,大家肯定看得懂,太煩的可以看后面的結果

function parseStartTag() {    



    // html ="
111
" // start = [" while ( // 匹配不到頭標簽的 >,開始匹配 屬性內容 // end = null ! (end = html.match(startTagClose)) && // 開始匹配 屬性內容 // attr = ["name=1", "name", "=" ] (attr = html.match(attribute)) ) { advance(attr[0].length); match.attrs.push(attr); } // 匹配到 起始標簽的 >,標簽屬性那些已經匹配完畢了 // 返回收集到的 標簽信息 if (end) { advance(end[0].length); // 如果是單標簽,那么 unarySlash 的值是 /,比如 match.unarySlash = end[1]; match.end = index; return match } } }

我們來記錄下這個方法會返回什么

比如

html = "
"

parseStartTag 處理之后會返回以下內容

{    

    tagName: "div",    

    attrs: [

        [" name=1", "name", "=" ,

         undefined, undefined, "1"]

    ],    

    unarySlash: "",    

    start: 0,    

    end: 12

}

其中 的屬性

start:頭標簽的 < 在 template 中的位置

end:頭標簽的 > 在 tempalte 中的位置

attrs:是一個二維數組,存放著所有頭標簽的 屬性信息

unarySlash:表示這個標簽是否是 單標簽。如果是 true,那么不是單標簽。如果是 false,那么就是單標簽。一切在于匹配頭標簽時,有沒有匹配到 /

通過 parseHTML 我們看到,parseStartTag 返回的 頭標簽信息,給了誰呢?

沒錯,傳給了 handleStartTag

handleStartTag

這個函數的作用

1、接收上一步收集的標簽信息

2、處理屬性,轉換一下其格式

3、保存進 stack,記錄 DOM 父子結構順序

function handleStartTag(match) {    



    var tagName = match.tagName;    

    var unarySlash = match.unarySlash;    



    // 判斷是不是單標簽,input,img 這些

    var unary = isUnaryTag$$1(tagName) || !!unarySlash;    



    var l = match.attrs.length;    

    var attrs = new Array(l);    



    // 把屬性數組轉換成對象
    for (var i = 0; i < l; i++) {        

        var args = match.attrs[i];    

   

        // args = [" name=1", "name", "=",

                undefined, undefined, "1" ]        

        var value = args[3] || args[4] || args[5] || "";

        attrs[i] = {            

            name: args[1],            

            value: value

        };
    }    

    

    // 不是單標簽,才存到 stack
    if (!unary) {
        stack.push({            

            tag: tagName,            

            attrs: attrs

        });
    }    



    if (options.start) {

        options.start(
            tagName, attrs, unary,
            match.start, match.end
        );
    }
}

最后,把該標簽得到的信息,傳給 options.start,幫助建立 template 的 ast(在 上篇文章 Compile - 源碼版 之 Parse 主要流程 中有說明=)

那么到這里,頭標簽 匹配完了

然后 template 被截斷成

"111"

文本處理的部分我們跳過,跳到尾標簽,所以 template 為

""

然后匹配到尾標簽,交給 parseEndTag 處理

那么進入我們的下一小節內容,處理尾標簽

處理尾標簽

在 parseHTML 中看到

當使用 endTag 這個正則成功匹配到尾標簽時,會調用 parseEndTag

而 這個函數呢,可能沒有那么好理解了,你可以先跳過源碼,翻到后面的解析

function parseEndTag(tagName, start, end) {    



    var pos, lowerCasedTagName;    



    // 從stack 最后查找匹配的 tagName 位置

    if (tagName) {        



        // 如果在 stack 中找不到,pos 最后是 -1

        for (pos = stack.length - 1; pos >= 0; pos--) {      

            if (stack[pos].tagName===tagName) break

        }
    }    

    else {        

        // 如果沒有提供標簽名,那么關閉所有存在 stack 中的 起始標簽

        pos = 0
    }    



    // 批量 stack pos 位置后的所有標簽

    if (pos >= 0) {        



        // 關閉 pos 位置之后所有的起始標簽,避免有些標簽沒有尾標簽

        // 比如 stack.len = 7 , pos=5 ,那么就關閉 最后兩個
        for (var i = stack.length - 1; i >= pos; i--) {    

            if (options.end) {

                options.end(stack[i].tag, start, end);
            }
        }        



        // 匹配完閉合標簽之后,就把 匹配了的標簽頭 給 移除了

        stack.length = pos;
    }
}

函數功能分為兩部分

1、找位置。從 stack 結尾,找到 tagName 所在位置 pos

2、批量閉合。閉合并移除 stack 在 pos 位置后的所有 tag

現在我們先給一個模板,然后慢慢解釋

現在已經連續匹配到三個 頭標簽,div,header,span

此時 stack= [ div, header, span ]

然后開始匹配到 ,然后去 stack 末尾找 span

確定 span 在 stack 的位置 pos 后,批量閉合stack 的 pos 后的所有標簽

為什么從末尾開始?

因為 stack 是按 template 的標簽順序存放的,肯定是先匹配到父標簽,再匹配到子標簽

碰到 尾標簽,肯定找最近匹配到的頭標簽,那么肯定是剛存入 stack 的,那么就是在 stack 的結尾

為什么閉合 pos位置后所有標簽?

因為怕有刁民不寫閉合標簽,比如模板是這樣

同樣,匹配完三個頭標簽

stack = [ div, header, span ]

接著匹配到 ,于是在 stack 末尾中找 header

在倒數第二個,那么 pos = 1

根據 stack 的長度, 遍歷一次 stack,閉合到末尾,就是閉合 stack[1], stack[2]

就是閉合 header 和 span 了

就是為了避免有屁民沒寫 尾標簽

你說單標簽沒有尾標簽啊?

是啊,但是單標簽 不存進 stack 啊哈哈哈

在 handleStartTag 中有處理哦

接下來你就可以去看 parseEndTag 的源碼了,肯定能看懂

怎么閉合的呢?

parseEndTag 就做了匹配 tag 位置 和容錯處理

主要實現閉合功能在調用 options.end 中,通過不斷地傳入尾標簽從而完成閉合功能

如果你看過上篇文章 Compile - 源碼版 之 Parse 主要流程 就知道,閉合是為了形成正確的節點關系樹

也可以說,是為了明確節點的父子關系

總結

通過這次我們知道了

1、標簽的匹配方法

2、怎么利用頭標簽收集信息

3、閉合標簽的處理方法

最后

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

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

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

相關文章

發表評論

0條評論

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