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

資訊專欄INFORMATION COLUMN

Vue源碼解析之Template轉化為AST

huangjinnan / 1585人閱讀

摘要:下面用具體代碼進行分析。匹配不到那么就是開始標簽,調用函數解析。如這里的轉化為加上是為了的下一步轉為函數,本文中暫時不會用到。再把轉化后的內容進。

什么是AST

在Vue的mount過程中,template會被編譯成AST語法樹,AST是指抽象語法樹(abstract syntax tree或者縮寫為AST),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式。

Virtual Dom

Vue的一個厲害之處就是利用Virtual DOM模擬DOM對象樹來優化DOM操作的一種技術或思路。
Vue源碼中虛擬DOM構建經歷 template編譯成AST語法樹 -> 再轉換為render函數 最終返回一個VNode(VNode就是Vue的虛擬DOM節點)
本文通過對源碼中AST轉化部分進行簡單提取,因為源碼中轉化過程還需要進行各種兼容判斷,非常復雜,所以筆者對主要功能代碼進行提取,用了300-400行代碼完成對template轉化為AST這個功能。下面用具體代碼進行分析。

 function parse(template) {
        var currentParent;    //當前父節點
        var root;            //最終返回出去的AST樹根節點
        var stack = [];
        parseHTML(template, {
            start: function start(tag, attrs, unary) {
               ......
            },
            end: function end() {
              ......
            },
            chars: function chars(text) {
               ......
            }
        })
        return root
    }

第一步就是調用parse這個方法,把template傳進來,這里假設template為

{{message}}

然后聲明3個變量
currentParent -> 存放當前父元素,root -> 最終返回出去的AST樹根節點,stack -> 一個棧用來輔助樹的建立
接著調用parseHTML函數進行轉化,傳入template和options(包含3個方法 start,end,chars 等下用到這3個函數再進行解釋)接下來先看parseHTML這個方法

 function parseHTML(html, options) {
        var stack = [];    //這里和上面的parse函數一樣用到stack這個數組 不過這里的stack只是為了簡單存放標簽名 為了和結束標簽進行匹配的作用
        var isUnaryTag$$1 = isUnaryTag;   //判斷是否為自閉合標簽
        var index = 0;
        var last;
        while (html) {
            //  第一次進入while循環時,由于字符串以<開頭,所以進入startTag條件,并進行AST轉換,最后將對象彈入stack數組中
            last = html;
            var textEnd = html.indexOf("<");
            if (textEnd === 0) {     // 此時字符串是不是以<開頭
                // End tag:
                var endTagMatch = html.match(endTag);
                if (endTagMatch) {
                    var curIndex = index;
                    advance(endTagMatch[0].length);
                    parseEndTag(endTagMatch[1], curIndex, index);
                    continue
                }

                // Start tag:    // 匹配起始標簽
                var startTagMatch = parseStartTag();    //處理后得到match
                if (startTagMatch) {
                    handleStartTag(startTagMatch);
                    continue
                }
            }

            // 初始化為undefined 這樣安全且字符數少一點
            var text = (void 0), rest = (void 0), next = (void 0);
            if (textEnd >= 0) {      // 截取<字符索引 => 
這里截取到閉合的< rest = html.slice(textEnd); //截取閉合標簽 // 處理文本中的<字符 // 獲取中間的字符串 => {{message}} text = html.substring(0, textEnd); //截取到閉合標簽前面部分 advance(textEnd); //切除閉合標簽前面部分 } // 當字符串沒有<時 if (textEnd < 0) { text = html; html = ""; } // // 處理文本 if (options.chars && text) { options.chars(text); } } }

函數進入while循環對html進行獲取<標簽索引 var textEnd = html.indexOf("<");如果textEnd === 0 說明當前是標簽或者 再用正則匹配是否當前是結束標簽。var endTagMatch = html.match(endTag); 匹配不到那么就是開始標簽,調用parseStartTag()函數解析。

function parseStartTag() {      //返回匹配對象
    var start = html.match(startTagOpen);         // 正則匹配
    if (start) {
        var match = {
            tagName: start[1],       // 標簽名(div)
            attrs: [],               // 屬性
            start: index             // 游標索引(初始為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);      // 標記結束位置
            match.end = index;      //這里的index 是在 parseHTML就定義 在advance里面相加
            return match         // 返回匹配對象 起始位置 結束位置 tagName attrs
        }
    }
}

該函數主要是為了構建一個match對象,對象里面包含tagName(標簽名),attrs(標簽的屬性),start(<左開始標簽在template中的位置),end(>右開始標簽在template中的位置) 如template =

{{message}}
程序第一次進入該函數 匹配的是div標簽 所以tagName就是div
start:0 end:14 如圖:

接著把match返回出去 作為調用handleStartTag的參數

var startTagMatch = parseStartTag();    //處理后得到match
if (startTagMatch) {
    handleStartTag(startTagMatch);
    continue
}

接下來看handleStartTag這個函數:

 function handleStartTag(match) {
    var tagName = match.tagName;
    var unary = isUnaryTag$$1(tagName)  //判斷是否為閉合標簽 
    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);
    }
    }

函數中分為3部分 第一部分是for循環是對attrs進行轉化,我們從上一步的parseStartTag()得到的match對象中的attrs屬性如圖

當時attrs是上面圖這樣子滴 我們通過這個循環把它轉化為只帶name 和 value這2個屬性的對象 如圖:

接著判斷如果不是自閉合標簽,把標簽名和屬性推入棧中(注意 這里的stack這個變量在parseHTML中定義,作用是為了存放標簽名 為了和結束標簽進行匹配的作用。)接著調用最后一步 options.start 這里的options就是我們在parse函數中 調用parseHTML是傳進來第二個參數的那個對象(包含start end chars 3個方法函數) 這里開始看options.start這個函數的作用:

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);
    }
}

這個函數中 生成element對象 再連接元素的parent 和 children節點 最終push到棧中
此時棧中第一個元素生成 如圖:

完成了while循環的第一次執行,進入第二次循環執行,這個時候html變成{{message}}

接著截取到 處理過程和第一次一致 經過這次循環stack中元素如圖:

接著繼續執行第三個循環 這個時候是處理文本節點了 {{message}}

// 初始化為undefined 這樣安全且字符數少一點
var text = (void 0), rest = (void 0), next = (void 0);
if (textEnd >= 0) {      // 截取<字符索引 => 
這里截取到閉合的< rest = html.slice(textEnd); //截取閉合標簽 // 處理文本中的<字符 // 獲取中間的字符串 => {{message}} text = html.substring(0, textEnd); //截取到閉合標簽前面部分 advance(textEnd); //切除閉合標簽前面部分 } // 當字符串沒有<時 if (textEnd < 0) { text = html; html = ""; } // 另外一個函數 if (options.chars && text) { options.chars(text); }

這里的作用就是把文本提取出來 調用options.chars這個函數 接下來看options.chars

chars: function chars(text) {
    if (!currentParent) {   //如果沒有父元素 只是文本
        return
    }

    var children = currentParent.children;  //取出children
    // text => {{message}}
    if (text) {
        var expression;
        if (text !== " " && (expression = parseText(text))) {
            // 將解析后的text存進children數組
            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}}還是簡單的文本xxx,如果是簡單的文本 push進父元素的children里面,type設置為3,如果是字符模板{{xxx}},調用parseText轉化。如這里的{{message}}轉化為 _s(message)(加上_s是為了AST的下一步轉為render函數,本文中暫時不會用到。) 再把轉化后的內容push進children。

又走完一個循環了,這個時候html =

剩下2個結束標簽進行匹配了

  var endTagMatch = html.match(endTag);
    if (endTagMatch) {
        var curIndex = index;
        advance(endTagMatch[0].length);
        parseEndTag(endTagMatch[1], curIndex, index);
        continue
    }

接下來看parseEndTag這個函數 傳進來了標簽名 開始索引和結束索引

  function parseEndTag(tagName, start, end) {
    var pos, lowerCasedTagName;
    if (tagName) {
        lowerCasedTagName = tagName.toLowerCase();
    }
    // Find the closest opened tag of the same type
    if (tagName) { // 獲取最近的匹配標簽
        for (pos = stack.length - 1; pos >= 0; pos--) {
            // 提示沒有匹配的標簽
            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;
}

這里首先找到棧中對應的開始標簽的索引pos,再從該索引開始到棧頂的所以元素調用options.end這個函數

 end: function end() {
    // pop stack
    stack.length -= 1;
    currentParent = stack[stack.length - 1];
},

把棧頂元素出棧,因為這個元素已經匹配到結束標簽了,再把當前父元素更改。終于走完了,把html的內容循環完,最終return root 這個root就是我們所要得到的AST

這只是Vue的冰山一角,文中有什么不對的地方請大家幫忙指正,本人最近也一直在學習Vue的源碼,希望能夠拿出來與大家一起分享經驗,接下來會繼續更新后續的源碼,如果覺得有幫忙請給個Star哈
github地址為:https://github.com/zwStar/vue... 歡迎各位star或issues

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

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

相關文章

發表評論

0條評論

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