欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue實(shí)現(xiàn)文本編譯詳情

 更新時間:2022年08月15日 10:17:15   作者:夏日  
這篇文章主要介紹了Vue實(shí)現(xiàn)文本編譯詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下,希望對你的學(xué)習(xí)有所幫助

Vue實(shí)現(xiàn)文本編譯詳情

模板編譯

數(shù)據(jù)劫持中,我們完成了Vuedata選項(xiàng)中數(shù)據(jù)的初始操作。這之后需要將html字符串編譯為render函數(shù),其核心邏輯如下:

render函數(shù)的情況下會直接使用傳入的render函數(shù),而在沒有render函數(shù)的情況下,需要將template編譯為render函數(shù)。

具體邏輯如下:

  • 獲取template字符串
  • template字符串解析為ast抽象語法樹
  • ast抽象語法樹生成代碼字符串
  • 將字符串處理為render函數(shù)賦值給vm.$options.render

獲取template字符串

在進(jìn)行template解析之前,會進(jìn)行一系列的條件處理,得到最終的template其處理邏輯如下:

src/init.js中書寫如下代碼:

/**
 * 將字符串處理為dom元素
 * @param el
 * @returns {Element|*}
 */
function query (el) {
  if (typeof el === 'string') {
    return document.querySelector(el);
  }
  return el;
}

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    const vm = this;
    vm.$options = options;
    initState(vm);
    const { el } = options;
    // el選項(xiàng)存在,會將el通過vm.$mount方法進(jìn)行掛載
    // el選項(xiàng)如果不存在,需要手動調(diào)用vm.$mount方法來進(jìn)行組件的掛載
    if (el) {
      vm.$mount(el);
    }
  };
  Vue.prototype.$mount = function (el) {
    el = query(el);
    const vm = this;
    const options = vm.$options;
    if (!options.render) { // 有render函數(shù),優(yōu)先處理render函數(shù)
      let template = options.template;
      // 沒有template,使用el.outerHTML作為template
      if (!template && el) {
        template = el.outerHTML;
      }
      options.render = compileToFunctions(template);
    }
  };
}

當(dāng)我們得到最終的template后,需要調(diào)用compileToFunctionstemplate轉(zhuǎn)換為render函數(shù)。在compileToFunctions中就是模板編譯的主要邏輯。

創(chuàng)建src/compiler/index.js文件,其代碼如下:

export function compileToFunctions (template) {
  // 將html解析為ast語法樹
  const ast = parseHtml(template);
  // 通過ast語法樹生成代碼字符串
  const code = generate(ast);
  // 將字符串轉(zhuǎn)換為函數(shù)
  return new Function(`with(this){return $[code]}`);
}

解析html

當(dāng)拿到對應(yīng)的html字符串后,需要通過正則來將其解析為ast抽象語法樹。簡單來說就是將html處理為一個樹形結(jié)構(gòu),可以很好的表示每個節(jié)點(diǎn)的父子關(guān)系。

下面是一段html,以及表示它的ast:

<body>
<div id="app">
  hh
  <div id="aa" style="font-size: 18px;">hello {{name}} world</div>
</div>
<script>
  const vm = new Vue({
    el: '#app',
    data () {
      return {
        name: 'zs',
      };
    },
  });
</script>
</body>

const ast = {
  tag: 'div', // 標(biāo)簽名
  attrs: [{ name: 'id', value: 'app' }], // 屬性數(shù)組 
  type: 1, // type:1 是元素,type: 3 是文本
  parent: null, // 父節(jié)點(diǎn)
  children: [] // 孩子節(jié)點(diǎn)
}

html的解析邏輯如下:

  • 通過正則匹配開始標(biāo)簽的開始符號、匹配標(biāo)簽的屬性、匹配開始標(biāo)簽結(jié)束符號、匹配文本、匹配結(jié)束標(biāo)簽
  • while循環(huán)html字符串,每次刪除掉已經(jīng)匹配的字符串,直到html為空字符串時,說明整個文本匹配完成
  • 通過棧數(shù)據(jù)結(jié)構(gòu)來記錄所有正在處理的標(biāo)簽,并且根據(jù)標(biāo)簽的入棧出棧順序生成樹結(jié)構(gòu)

代碼中通過advance函數(shù)來一點(diǎn)點(diǎn)刪除被匹配的字符串,其邏輯比較簡單,只是對字符串進(jìn)行了截?。?/p>

// 刪除匹配的字符串
function advance (length) {
  html = html.slice(length);
}

首先處理開始標(biāo)簽和屬性。

<開頭的字符串為開始標(biāo)簽或結(jié)束標(biāo)簽,通過正則匹配開始標(biāo)簽,可以通過分組得到標(biāo)簽名。之后循環(huán)匹配標(biāo)簽的屬性,直到匹配到結(jié)尾標(biāo)簽。在這過程中要將匹配到的字符串通過advance進(jìn)行刪除。

export function parseHtml (html) {
  function parseStartTag () {
    const start = html.match(startTagOpen);
    if (start) {
      const match = { tag: start[1], attrs: [] };
      // 開始解析屬性,直到標(biāo)簽閉合
      advance(start[0].length);
      let end = html.match(startTagClose);
      let attr = html.match(attribute);
      // 循環(huán)處理屬性
      while (!end && attr) {
        match.attrs.push({
          name: attr[1],
          value: attr[3] || attr[4] || attr[5]
        });
        advance(attr[0].length);
        end = html.match(startTagClose);
        attr = html.match(attribute);
      }
      if (end) {
        advance(end[0].length);
      }
      return match;
    }
  }

  // 注意:在template中書寫模板時可能開始和結(jié)束會有空白
  html = html.trim();
  while (html) {
    // 開始和結(jié)束標(biāo)簽都會以 < 開頭
    const textEnd = html.indexOf('<');
    if (textEnd === 0) {
      // 處理開始標(biāo)簽
      const startTag = parseStartTag();
      if (startTag) {
        start(startTag.tag, startTag.attrs);
      }
      // some code ...
    }
    // some code...
  }
  return root;
}

在獲得開始標(biāo)簽的標(biāo)簽名和屬性后,通過start函數(shù),可以生成樹根以及每一個入棧標(biāo)簽對應(yīng)ast元素并確定父子關(guān)系:

// 樹 + 棧
function createASTElement (tag, attrs) {
  return {
    tag,
    type: 1,
    attrs,
    children: [],
    parent: null
  };
}

let root, currentParent;
const stack = [];

function start (tag, attrs) {
  const element = createASTElement(tag, attrs);
  if (!root) {
    root = element;
  } else {
    // 記錄父子關(guān)系
    currentParent.children.push(element);
    element.parent = currentParent;
  }
  currentParent = element;
  stack.push(element);
}

以一段簡單的html為例,我們畫圖看下其具體的出棧入棧邏輯:

<div id="app">
  <h2>
    hello world
    <span> xxx </span>
  </h2>
</div>

通過對象的引用關(guān)系,最終便能得到一個樹形結(jié)構(gòu)對象root。

解析完開始標(biāo)簽后,剩余的文本起始字符串可能為:

  • 下一個開始標(biāo)簽
  • 文本內(nèi)容
  • 結(jié)束標(biāo)簽

如果仍然是開始標(biāo)簽,會重復(fù)上述邏輯。如果是文本內(nèi)容,<字符的索引會大于0,只需要將[0, textEnd)之間的文本截取出來放到父節(jié)點(diǎn)的children中即可:

export function parseHtml (html) {

  // 樹 + 棧
  let root, currentParent;
  const stack = [];
  function char (text) {
    // 替換所有文本中的空格
    text = text.replace(/\s/g, '');
    if (currentParent && text) {
      // 將文本放到對應(yīng)的父節(jié)點(diǎn)的children數(shù)組中,其type為3,標(biāo)簽type為1
      currentParent.children.push({
        type: 3,
        text,
        parent: currentParent
      });
    }
  }

  while (html) {
    // some code ...
    //  < 在之后的位置,說明要處理的是文本內(nèi)容
    if (textEnd > 0) { // 處理文本內(nèi)容
      let text = html.slice(0, textEnd);
      if (text) {
        char(text);
        advance(text.length);
      }
    }
  }
  return root;
}

最后來處理結(jié)束標(biāo)簽。

匹配到結(jié)束標(biāo)簽時要將stack中最后一個元素出棧,更新currentParent,直到stack中的元素為空時。就得到了完整的ast抽象語法樹:

export function parseHtml (html) {
  // 樹 + 棧
  let root, currentParent;
  const stack = [];

  // 每次處理好前一個,最后將所有元素作為子元素push到root節(jié)點(diǎn)中
  function end (tag) { // 在結(jié)尾標(biāo)簽匹配時可以確立父子關(guān)系
    stack.pop();
    currentParent = stack[stack.length - 1];
  }

  while (html) {
    // 開始和結(jié)束標(biāo)簽都會以 < 開頭
    const textEnd = html.indexOf('<');
    if (textEnd === 0) {
      // some code ...
      // 處理結(jié)尾標(biāo)簽
      const endTagMatch = html.match(endTag);
      if (endTagMatch) {
        end(endTagMatch[1]);
        advance(endTagMatch[0].length);
      }
    }
    // some code ...  
  }
  return root;
}

到這里我們拿到了一個樹形結(jié)構(gòu)對象ast,接下來要根據(jù)這個樹形結(jié)構(gòu),遞歸生成代碼字符串

生成代碼字符串

Vue代碼生成可視化編輯器

先看下面一段html字符串生成的代碼字符串是什么樣子的:

<body>
<div id="app">
  hh
  <div id="aa" style="color: red;">hello {{name}} world</div>
</div>
<script>
  const vm = new Vue({
    el: '#app',
    data () {
      return {
        name: 'zs',
      };
    },
  });
</script>
</body>

最終得到的代碼字符串如下:

const code = `_c("div",{id:"app"},_v("hh"),_c("div"),{id:"aa",style:{color: "red"}},_v("hello"+_s(name)+"world"))`

最終會將上述代碼通過new Function(with(this) { return $[code]})轉(zhuǎn)換為render函數(shù),而在render函數(shù)執(zhí)行時通過call來將this指向vm 。所以代碼字符串中的函數(shù)和變量都會從vm上進(jìn)行查找。

下面是代碼字符串中用到的函數(shù)的含義:

  • _c : 創(chuàng)建虛擬元素節(jié)點(diǎn)createVElement
  • _v : 創(chuàng)建虛擬文本節(jié)點(diǎn)createTextVNode
  • _s : stringify對傳入的值執(zhí)行JSON.stringify

接下來開始介紹如何將ast樹形對象處理為上邊介紹到code。

創(chuàng)建src/compiler/generate.js文件,需要解析的內(nèi)容如下:

  • 標(biāo)簽
  • 屬性
  • 遞歸處理children
  • 文本

標(biāo)簽處理比較簡單,直接獲取ast.tag即可。

屬性在代碼字符串中是以對象的格式存在,而在ast中是數(shù)組的形式。這里需要遍歷數(shù)組,并將其namevalue處理為對象的鍵和值。需要注意style屬性要特殊處理

function genAttrs (attrs) {
  if (attrs.length === 0) {
    return 'undefined';
  }
  let str = '';
  for (let i = 0; i < attrs.length; i++) {
    const attr = attrs[i];
    if (attr.name === 'style') {
      const styleValues = attr.value.split(',');
      // 可以對對象使用JSON.stringify來進(jìn)行處理
      attr.value = styleValues.reduce((obj, item) => {
        const [key, val] = item.split(':');
        obj[key] = val;
        return obj;
      }, {});
    }
    str += `${attr.name}:${JSON.stringify(attr.value)}`;
    if (i !== attrs.length - 1) {
      str += ',';
    }
  }
  return `{${str}}`;
}

// some code ...

export function generate (el) {
  const children = genChildren(el.children);
  return `_c("${el.tag}", ${genAttrs(el.attrs)}${children ? ',' + children : ''})`;
}

在用,拼接對象時,也可以先將每一部分放到數(shù)組中,通過數(shù)組的join方法用,來拼接為字符串。

標(biāo)簽和屬性之后的參數(shù)都為孩子節(jié)點(diǎn),要以函數(shù)參數(shù)的形式用,進(jìn)行拼接,最終在生成虛擬節(jié)點(diǎn)時會通過...擴(kuò)展運(yùn)算符將其處理為一個數(shù)組:

function gen (child) {
  if (child.type === 1) {
    // 將元素處理為代碼字符串并返回
    return generate(child);
  } else if (child.type === 3) {
    return genText(child.text);
  }
}

// 將children處理為代碼字符串并返回
function genChildren (children) { // 將children用','拼接起來
  const result = [];
  for (let i = 0; i < children.length; i++) {
    const child = children[i];
    // 將生成結(jié)果放到數(shù)組中
    result.push(gen(child));
  }
  return result.join(',');
}
export function generate (el) {
  const children = genChildren(el.children);
  return `_c("${el.tag}", ${genAttrs(el.attrs)}${children ? ',' + children : ''})`;
}

在生成孩子節(jié)點(diǎn)時,需要判斷每一項(xiàng)的類型,如果是元素會繼續(xù)執(zhí)行generate方法來生成元素對應(yīng)的代碼字符串,如果是文本,需要通過genText方法來進(jìn)行處理:

const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;

function genText (text) {
  if (!defaultTagRE.test(text)) {
    return `_v(${JSON.stringify(text)})`;
  }
  // <div id="aa">hello {{name}} xx{{msg}} hh <span style="color: red" class="bb">world</span></div>
  const tokens = [];
  let lastIndex = defaultTagRE.lastIndex = 0;
  let match;
  while (match = defaultTagRE.exec(text)) {
    // 這里的先后順序如何確定? 通過match.index和lastIndex的大小關(guān)系
    // match.index === lastIndex時,說明此時是{{}}中的內(nèi)容,前邊沒有字符串
    if (match.index > lastIndex) {
      tokens.push(JSON.stringify(text.slice(lastIndex, match.index)));
    }
    // 然后將括號內(nèi)的元素放到數(shù)組中
    tokens.push(`_s(${match[1].trim()})`);
    lastIndex = defaultTagRE.lastIndex;
  }
  if (lastIndex < text.length) {
    tokens.push(JSON.stringify(text.slice(lastIndex)));
  }
  return `_v(${tokens.join('+')})`;
}

genText中會利用lastIndex以及match.index來循環(huán)處理每一段文本。由于正則添加了g標(biāo)識,每次匹配完之后,都會將lastIndex移動到下一次開始匹配的位置。最終匹配完所有的{{}} 文本后,match=null并且lastIndex=0,終止循環(huán)。

{{}}中的文本需要放到_s() 中,每段文本都會放到數(shù)組tokens中,最后將每段文本通過+拼接起來。最終在render函數(shù)執(zhí)行時,會進(jìn)行字符串拼接操作,然后展示到頁面中。

代碼中用到的lastIndexmatch.index含義分別如下:

  • lastIndex: 字符串下次開始匹配的位置對應(yīng)的索引
  • match.index: 匹配到的字符串在原字符串中的索引

其匹配邏輯如下圖所示:

在上邊的邏輯完成后,會得到最終的code,下面需要將code處理為render函數(shù)。

生成render函數(shù)

js中,new Function可以通過字符串來創(chuàng)建一個函數(shù)。利用我們之前生成的字符串再結(jié)合new Function便可以得到一個函數(shù)。

而字符串中的變量最終會到vm實(shí)例上進(jìn)行取值,with可以指定變量的作用域,下面是一個簡單的例子:

const obj = { a: 1, b: 2 }
with (obj) {
  console.log(a) // 1
  console.log(b) // 2
}

利用new Functionwith的相關(guān)特性,可以得到如下代碼:

const render = new Function(`with(this){return $[code]}`)

到這里,我們便完成了compileToFunctions函數(shù)的功能,實(shí)現(xiàn)了文章開始時這行代碼的邏輯:

vm.$options.render = compileFunctions(template)

結(jié)語

文本中代碼主要涉及的知識如下:

  • 通過棧+樹這倆種數(shù)據(jù)結(jié)構(gòu),通過正則將html解析為樹
  • 利用正則表達(dá)式來進(jìn)行字符串的匹配實(shí)現(xiàn)相應(yīng)的邏輯

文章中介紹到的整個邏輯,也是Vue在文本編譯過程中的核心邏輯。希望小伙伴在讀完本文之后,可以對Vue如何解析template有更深的理解,并可以嘗試閱讀其源碼。

到此這篇關(guān)于Vue實(shí)現(xiàn)文本編譯詳情的文章就介紹到這了,更多相關(guān)Vue文本編譯內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue3中update:modelValue的使用與不生效問題解決

    vue3中update:modelValue的使用與不生效問題解決

    現(xiàn)在vue3的使用越來越普遍了,vue3這方面的學(xué)習(xí)我們要趕上,下面這篇文章主要給大家介紹了關(guān)于vue3中update:modelValue的使用與不生效問題的解決方法,需要的朋友可以參考下
    2022-03-03
  • vuejs2.0運(yùn)用原生js實(shí)現(xiàn)簡單的拖拽元素功能示例

    vuejs2.0運(yùn)用原生js實(shí)現(xiàn)簡單的拖拽元素功能示例

    本篇文章主要介紹了vuejs2.0運(yùn)用原生js實(shí)現(xiàn)簡單的拖拽元素功能示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-02-02
  • 基于vue.js實(shí)現(xiàn)分頁查詢功能

    基于vue.js實(shí)現(xiàn)分頁查詢功能

    這篇文章主要為大家詳細(xì)介紹了基于vue.js實(shí)現(xiàn)分頁查詢功能,vue.js實(shí)現(xiàn)數(shù)據(jù)庫分頁,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • Vue零基礎(chǔ)入門之模板語法與數(shù)據(jù)綁定及Object.defineProperty方法詳解

    Vue零基礎(chǔ)入門之模板語法與數(shù)據(jù)綁定及Object.defineProperty方法詳解

    這篇文章主要介紹了Vue初學(xué)基礎(chǔ)中的模板語法、數(shù)據(jù)綁定、Object.defineProperty方法等基礎(chǔ),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09
  • highCharts提示框中顯示當(dāng)前時間的方法

    highCharts提示框中顯示當(dāng)前時間的方法

    今天小編就為大家分享一篇關(guān)于highCharts提示框中顯示當(dāng)前時間的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • vue3的hooks用法總結(jié)

    vue3的hooks用法總結(jié)

    vue3中的hooks其實(shí)是函數(shù)的寫法,就是將文件的一些單獨(dú)功能的js代碼進(jìn)行抽離出來,放到單獨(dú)的js文件中,這篇文章主要介紹了一文掌握vue3中hooks的介紹及用法,需要的朋友可以參考下
    2023-04-04
  • laravel5.3 vue 實(shí)現(xiàn)收藏夾功能實(shí)例詳解

    laravel5.3 vue 實(shí)現(xiàn)收藏夾功能實(shí)例詳解

    這篇文章主要介紹了laravel5.3 vue 實(shí)現(xiàn)收藏夾功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2018-01-01
  • antd?Vue實(shí)現(xiàn)Login登錄頁面布局案例詳解?附帶驗(yàn)證碼驗(yàn)證功能

    antd?Vue實(shí)現(xiàn)Login登錄頁面布局案例詳解?附帶驗(yàn)證碼驗(yàn)證功能

    這篇文章主要介紹了antd?Vue實(shí)現(xiàn)Login登錄頁面布局案例詳解附帶驗(yàn)證碼驗(yàn)證功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • Vue實(shí)現(xiàn)簡單的跑馬燈

    Vue實(shí)現(xiàn)簡單的跑馬燈

    這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)簡單的跑馬燈,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • vue3+ant?design的form數(shù)組表單校驗(yàn)方法

    vue3+ant?design的form數(shù)組表單校驗(yàn)方法

    這篇文章主要介紹了vue3+ant?design的form數(shù)組表單,如何校驗(yàn),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-09-09

最新評論