最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue源碼解析之Template轉(zhuǎn)化為AST的實(shí)現(xiàn)方法

 更新時(shí)間:2018年12月14日 11:27:32   作者:zwStar  
這篇文章主要介紹了Vue源碼解析之Template轉(zhuǎn)化為AST的實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

什么是AST

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

Virtual Dom

Vue的一個(gè)厲害之處就是利用Virtual DOM模擬DOM對(duì)象樹來優(yōu)化DOM操作的一種技術(shù)或思路。

Vue源碼中虛擬DOM構(gòu)建經(jīng)歷 template編譯成AST語法樹 -> 再轉(zhuǎn)換為render函數(shù) 最終返回一個(gè)VNode(VNode就是Vue的虛擬DOM節(jié)點(diǎn))

本文通過對(duì)源碼中AST轉(zhuǎn)化部分進(jìn)行簡(jiǎn)單提取,因?yàn)樵创a中轉(zhuǎn)化過程還需要進(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樹根節(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)來,這里假設(shè)template為 <div id="app"><span>{{message}}</span></div>

然后聲明3個(gè)變量

currentParent -> 存放當(dāng)前父元素,root -> 最終返回出去的AST樹根節(jié)點(diǎn),stack -> 一個(gè)棧用來輔助樹的建立

接著調(diào)用parseHTML函數(shù)進(jìn)行轉(zhuǎn)化,傳入template和options(包含3個(gè)方法 start,end,chars 等下用到這3個(gè)函數(shù)再進(jìn)行解釋)接下來先看parseHTML這個(gè)方法

function parseHTML(html, options) {
    var stack = [];  //這里和上面的parse函數(shù)一樣用到stack這個(gè)數(shù)組 不過這里的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í),由于字符串以<開頭,所以進(jìn)入startTag條件,并進(jìn)行AST轉(zhuǎn)換,最后將對(duì)象彈入stack數(shù)組中
      last = html;
      var textEnd = html.indexOf('<');
      if (textEnd === 0) {   // 此時(shí)字符串是不是以<開頭
        // 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) {   // 截取<字符索引 => </div> 這里截取到閉合的<
        rest = html.slice(textEnd); //截取閉合標(biāo)簽
        // 處理文本中的<字符
        // 獲取中間的字符串 => {{message}}
        text = html.substring(0, textEnd); //截取到閉合標(biāo)簽前面部分
        advance(textEnd);        //切除閉合標(biāo)簽前面部分

      }
      // 當(dāng)字符串沒有<時(shí)
      if (textEnd < 0) {
        text = html;
        html = '';
      }
      // // 處理文本
      if (options.chars && text) {
        options.chars(text);
      }
    }
  }

函數(shù)進(jìn)入while循環(huán)對(duì)html進(jìn)行獲取<標(biāo)簽索引 var textEnd = html.indexOf('<');如果textEnd === 0 說明當(dāng)前是標(biāo)簽<xxx>或者</xxx> 再用正則匹配是否當(dāng)前是結(jié)束標(biāo)簽</xxx>。var endTagMatch = html.match(endTag); 匹配不到那么就是開始標(biāo)簽,調(diào)用parseStartTag()函數(shù)解析。

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(<左開始標(biāo)簽在template中的位置),end(>右開始標(biāo)簽在template中的位置) 如template = <div id="app"><div><span>{{message}}</span></div></div> 程序第一次進(jìn)入該函數(shù) 匹配的是div標(biāo)簽 所以tagName就是div
start:0 end:14 如圖:

接著把match返回出去 作為調(diào)用handleStartTag的參數(shù)

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

接下來看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是上面圖這樣子滴 我們通過這個(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)來第二個(gè)參數(shù)的那個(gè)對(duì)象(包含start end chars 3個(gè)方法函數(shù)) 這里開始看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變成<span>{{message}}</span></div> 接著截取到<span> 處理過程和第一次一致 經(jīng)過這次循環(huán)stack中元素如圖:

接著繼續(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) {   // 截取<字符索引 => </div> 這里截取到閉合的<
  rest = html.slice(textEnd); //截取閉合標(biāo)簽
  // 處理文本中的<字符
  // 獲取中間的字符串 => {{message}}
  text = html.substring(0, textEnd); //截取到閉合標(biāo)簽前面部分
  advance(textEnd);        //切除閉合標(biāo)簽前面部分
}
// 當(dāng)字符串沒有<時(shí)
if (textEnd < 0) {
  text = html;
  html = '';
}
// 另外一個(gè)函數(shù)
if (options.chars && text) {
  options.chars(text);
}

這里的作用就是把文本提取出來 調(diào)用options.chars這個(gè)函數(shù) 接下來看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存進(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 = </span></div> 剩下2個(gè)結(jié)束標(biāo)簽進(jìn)行匹配了

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

接下來看parseEndTag這個(gè)函數(shù) 傳進(jìn)來了標(biāo)簽名 開始索引和結(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--) {
      // 提示沒有匹配的標(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)的開始標(biāo)簽的索引pos,再從該索引開始到棧頂?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ī)兔χ刚?,本人最近也一直在學(xué)習(xí)Vue的源碼,希望能夠拿出來與大家一起分享經(jīng)驗(yàn),接下來會(huì)繼續(xù)更新后續(xù)的源碼,如果覺得有幫忙請(qǐng)給個(gè)Star哈

github地址為:https://github.com/zwStar/vue-ast 歡迎各位star或issues

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • vue登錄成功之后的token處理詳細(xì)步驟

    vue登錄成功之后的token處理詳細(xì)步驟

    Token是身份驗(yàn)證后服務(wù)端返回的令牌,常用于訪問授權(quán)API和頁面權(quán)限校驗(yàn),Token數(shù)據(jù)可存儲(chǔ)在本地或Vuex中,本地存儲(chǔ)可實(shí)現(xiàn)數(shù)據(jù)持久化,這篇文章主要介紹了vue登錄成功之后的token處理詳細(xì)步驟,需要的朋友可以參考下
    2024-10-10
  • vue-devtools的安裝與使用教程

    vue-devtools的安裝與使用教程

    vue-devtools是一款基于chrome游覽器的插件,用于調(diào)試vue應(yīng)用,這可以極大地提高我們的調(diào)試效率,這篇文章主要介紹了vue-devtools的安裝與使用教程,需要的朋友可以參考下
    2023-03-03
  • vue 防止多次點(diǎn)擊的實(shí)踐

    vue 防止多次點(diǎn)擊的實(shí)踐

    本文主要介紹了vue 防止多次點(diǎn)擊,可以有效防止惡意點(diǎn)擊,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Vue 框架之鍵盤事件、健值修飾符、雙向數(shù)據(jù)綁定

    Vue 框架之鍵盤事件、健值修飾符、雙向數(shù)據(jù)綁定

    這篇文章主要介紹了Vue 框架之鍵盤事件、健值修飾符、雙向數(shù)據(jù)綁定問題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-11-11
  • vue實(shí)現(xiàn)分頁組件

    vue實(shí)現(xiàn)分頁組件

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)分頁組件的具體代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • Vant主題定制如何修改顏色樣式

    Vant主題定制如何修改顏色樣式

    這篇文章主要介紹了Vant主題定制如何修改顏色樣式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Element中select多數(shù)據(jù)加載優(yōu)化的實(shí)現(xiàn)

    Element中select多數(shù)據(jù)加載優(yōu)化的實(shí)現(xiàn)

    本文主要介紹了Element中select多數(shù)據(jù)加載優(yōu)化的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Vue 子組件與數(shù)據(jù)傳遞問題及注意事項(xiàng)

    Vue 子組件與數(shù)據(jù)傳遞問題及注意事項(xiàng)

    這篇文章主要介紹了Vue子組件與數(shù)據(jù)傳遞問題及需要注意事項(xiàng),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-07-07
  • vue服務(wù)端渲染添加緩存的方法

    vue服務(wù)端渲染添加緩存的方法

    vue服務(wù)區(qū)緩存分為頁面緩存、組建緩存和接口緩存,本文通過實(shí)例代碼逐一給大家介紹,本文重點(diǎn)給大家介紹vue服務(wù)端渲染添加緩存的方法,感興趣的朋友跟隨小編一起看看吧
    2018-09-09
  • Vue項(xiàng)目在IE瀏覽器頁面白屏且報(bào)錯(cuò)SCRIPT1010:缺少標(biāo)識(shí)符問題

    Vue項(xiàng)目在IE瀏覽器頁面白屏且報(bào)錯(cuò)SCRIPT1010:缺少標(biāo)識(shí)符問題

    Vue項(xiàng)目在谷歌瀏覽器中正常運(yùn)行,但在IE瀏覽器中出現(xiàn)問題,如白屏和控制臺(tái)報(bào)錯(cuò),解決過程包括檢查IE設(shè)置、調(diào)整編輯器配置、引入兼容性插件、使用productionSourceMap定位錯(cuò)誤、檢查插件依賴和版本,以及重新構(gòu)建項(xiàng)目
    2024-09-09

最新評(píng)論

抚宁县| 榆树市| 荔浦县| 茂名市| 汉阴县| 翼城县| 榆社县| 大理市| 博白县| 大余县| 道孚县| 兴海县| 广饶县| 嘉鱼县| 峡江县| 太原市| 离岛区| 土默特左旗| 来安县| 沙坪坝区| 佛坪县| 淮北市| 颍上县| 久治县| 汨罗市| 砀山县| 名山县| 甘孜| 七台河市| 赣榆县| 齐河县| 麟游县| 凯里市| 偃师市| 尼木县| 澄城县| 前郭尔| 英超| 绿春县| 敖汉旗| 华安县|