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

Vue渲染流程步驟詳解

 更新時(shí)間:2023年07月27日 09:33:58   作者:大可-  
在Vue里渲染一塊內(nèi)容,會(huì)有四個(gè)流程步驟,那么該怎么理解這個(gè)流程呢,所以本文就給大家詳細(xì)講解一下Vue 渲染流程,文中有纖細(xì)的代碼示例供大家參考,需要的朋友可以參考下

在 Vue 里渲染一塊內(nèi)容,會(huì)有以下步驟及流程:

第一步,解析語(yǔ)法,生成AST

第二步,根據(jù)AST結(jié)果,完成data數(shù)據(jù)初始化

第三步,根據(jù)AST結(jié)果和DATA數(shù)據(jù)綁定情況,生成虛擬DOM

第四步,將虛擬DOM 生成真正的DOM插入到頁(yè)面中,進(jìn)行頁(yè)面渲染。

那怎么理解這個(gè)流程呢?

一、解析語(yǔ)法生成AST

AST 語(yǔ)法樹(shù),實(shí)際就是抽象語(yǔ)法樹(shù)(Abstract Syntax Tree),是指通過(guò)構(gòu)建語(yǔ)法樹(shù)的形式將源代碼中的語(yǔ)句映射到樹(shù)中的每一個(gè)節(jié)點(diǎn)上。

DOM 結(jié)構(gòu)樹(shù),也是AST中的一種,把HTML DOM語(yǔ)法解析并生成最終頁(yè)面。

我們?cè)敿?xì)看看這個(gè)過(guò)程:

1、捕獲語(yǔ)法

在生成AST的過(guò)程中,會(huì)涉及到編譯器的原理, 會(huì)經(jīng)過(guò)以下過(guò)程:

(1)、語(yǔ)法分析

語(yǔ)法分析的任務(wù)是在詞法分析的基礎(chǔ)上將單詞序列組合成各類語(yǔ)法短語(yǔ)。如 :程序、語(yǔ)句、表達(dá)式等。語(yǔ)法分析程序判斷源程序在結(jié)構(gòu)上是否正確, 如 v-if` / v-for 這樣的指令 ,也有``這樣的自定義 DOM 標(biāo)簽,還有`click`/`props 這樣的簡(jiǎn)化綁定語(yǔ)法。需要將它們一一解析出來(lái),并相應(yīng)地進(jìn)行后續(xù)處理。

(2)、語(yǔ)義分析

語(yǔ)義分析是審查源程序有無(wú)語(yǔ)義錯(cuò)誤,為代碼生成階段收集類型信息,一般類型檢查也會(huì)在這個(gè)過(guò)程中進(jìn)行。如我們綁定了某個(gè)不存在的變量或者事件,又或者是使用了某個(gè)未定義的自定義組件等,都會(huì)在這個(gè)階段進(jìn)行報(bào)錯(cuò)提示。

(3) 、生成 AST

在Vue 里,語(yǔ)法分析、語(yǔ)義分析基本上是通過(guò)正則的方式來(lái)處理,生成 AST其實(shí)就是將解析出來(lái)的元素、指令、屬性、父子節(jié)點(diǎn)關(guān)系等內(nèi)容進(jìn)行處理,得到一個(gè) AST 對(duì)象,以下是簡(jiǎn)化后的源碼:

/**
 *  HTML編譯成AST對(duì)象
 */
export function parse(
  template: string,
  options: CompilerOptions
): ASTElement | void 
{
  // 返回AST對(duì)象
  // 篇幅原因,一些前置定義省略
  // 此處開(kāi)始解析HTML模板
  parseHTML(template, {
    expectHTML: options.expectHTML,
    isUnaryTag: options.isUnaryTag,
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    start(tag, attrs, unary) {
      // 一些前置檢查和設(shè)置、兼容處理此處省略
      // 此處定義了初始化的元素AST對(duì)象
      const element: ASTElement = {
        type: 1,
        tag,
        attrsList: attrs,
        attrsMap: makeAttrsMap(attrs),
        parent: currentParent,
        children: []
      };
      // 檢查元素標(biāo)簽是否合法(不是保留命名)
      if (isForbiddenTag(element) && !isServerRendering()) {
        element.forbidden = true;
        process.env.NODE_ENV !== "production" &&
          warn(
            "Templates should only be responsible for mapping the state to the " +
              "UI. Avoid placing tags with side-effects in your templates, such as " +
              `<${tag}>` +
              ", as they will not be parsed."
          );
      }
      // 執(zhí)行一些前置的元素預(yù)處理
      for (let i = 0; i < preTransforms.length; i++) {
        preTransforms[i](element, options);
      }
      // 是否原生元素
      if (inVPre) {
        // 處理元素元素的一些屬性
        processRawAttrs(element);
      } else {
        // 處理指令,此處包括v-for/v-if/v-once/key等等
        processFor(element);
        processIf(element);
        processOnce(element);
        processKey(element); // 刪除結(jié)構(gòu)屬性
        // 確定這是否是一個(gè)簡(jiǎn)單的元素
        element.plain = !element.key && !attrs.length;
        // 處理ref/slot/component等屬性
        processRef(element);
        processSlot(element);
        processComponent(element);
        for (let i = 0; i < transforms.length; i++) {
          transforms[i](element, options);
        }
        processAttrs(element);
      }
      // 后面還有一些父子節(jié)點(diǎn)等處理,此處省略
    }
    // 其他省略
  });
  return root;
}

2、DOM 元素捕獲

假如我們需要捕獲一個(gè)<div>元素,再生成一個(gè)<div>元素。

有一段模板,我們可以對(duì)它進(jìn)行捕獲:

<div>
  <a>111</a>
  <p>222<span>333</span> </p>
</div>

捕獲后我們可以得到這樣一個(gè)對(duì)象:

divObj = {
  dom: {
    type: "dom",
    ele: "div",
    nodeIndex: 0,
    children: [
      {
        type: "dom",
        ele: "a",
        nodeIndex: 1,
        children: [{ type: "text", value: "111" }]
      },
      {
        type: "dom",
        ele: "p",
        nodeIndex: 2,
        children: [
          { type: "text", value: "222" },
          {
            type: "dom",
            ele: "span",
            nodeIndex: 3,
            children: [{ type: "text", value: "333" }]
          }
        ]
      }
    ]
  }
};
 

這個(gè)對(duì)象保存了我們需要的一些信息:

HTML元素里需要綁定哪些變量,因?yàn)樽兞扛碌臅r(shí)候需要更新該節(jié)點(diǎn)內(nèi)容。

以怎樣的方式來(lái)拼接,是否有邏輯指令,如v-if、v-for等

哪些節(jié)點(diǎn)綁定了什么監(jiān)聽(tīng)事件,是否匹配一些常用的事件能力支持

Vue 會(huì)根據(jù) AST 對(duì)象生成一段可執(zhí)行的代碼,我們看看這部分的實(shí)現(xiàn):

// 生成一個(gè)元素
function genElement(el: ASTElement): string {
  // 根據(jù)該元素是否有相關(guān)的指令、屬性語(yǔ)法對(duì)象,來(lái)進(jìn)行對(duì)應(yīng)的代碼生成
  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el);
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el);
  } else if (el.for && !el.forProcessed) {
    return genFor(el);
  } else if (el.if && !el.ifProcessed) {
    return genIf(el);
  } else if (el.tag === "template" && !el.slotTarget) {
    return genChildren(el) || "void 0";
  } else if (el.tag === "slot") {
    return genSlot(el);
  } else {
    // component或者element的代碼生成
    let code;
    if (el.component) {
      code = genComponent(el.component, el);
    } else {
      const data = el.plain ? undefined : genData(el);
      const children = el.inlineTemplate ? null : genChildren(el, true);
      code = `_c('${el.tag}'${
        data ? `,${data}` : "" // data
      }${
        children ? `,${children}` : "" // children
      })`;
    }
    // 模塊轉(zhuǎn)換
    for (let i = 0; i < transforms.length; i++) {
      code = transforms[i](el, code);
    }
    // 返回最后拼裝好的可執(zhí)行的代碼
    return code;
  }
}

3、模板引擎賦能

通過(guò)以上介紹,或許大家會(huì)說(shuō),原本就是一個(gè)<div>,經(jīng)過(guò) AST 生成一個(gè)對(duì)象,最終還是生成一個(gè)<div>,這不是多余的步驟嗎?

其實(shí) ,在這個(gè)過(guò)程中我們可以實(shí)現(xiàn)一些功能:

排除無(wú)效 DOM 元素,并在構(gòu)建過(guò)程可進(jìn)行報(bào)錯(cuò)

使用自定義組件的時(shí)候,可匹配出來(lái)

可方便地實(shí)現(xiàn)數(shù)據(jù)綁定、事件綁定等功能

為虛擬 DOM Diff 過(guò)程打下鋪墊

HTML 轉(zhuǎn)義預(yù)防 XSS 漏洞

通用的模板引擎能處理很多低效又重復(fù)的工作,例如瀏覽器兼容、全局事件的統(tǒng)一管理和維護(hù)、模板更新的虛擬 DOM 機(jī)制、樹(shù)狀組織管理組件。這樣我們知道了模板引擎都做了什么事情后,就可以區(qū)分 Vue 框架提供的能力和我們需要自行處理的邏輯,可以更專注于業(yè)務(wù)開(kāi)發(fā)。

二、虛擬DOM

虛擬 DOM 大概可分成三個(gè)過(guò)程:

第一步,用 JS 對(duì)象模擬 DOM 樹(shù),得到一棵虛擬 DOM 樹(shù)。

第二步,當(dāng)頁(yè)面數(shù)據(jù)變更時(shí),生成新的虛擬 DOM 樹(shù),比較新舊兩棵虛擬 DOM 樹(shù)的差異。

第三步,把差異應(yīng)用到真正的 DOM 樹(shù)上。

1、用 JS 對(duì)象模擬 DOM 樹(shù)

為什么要用到虛擬 DOM ? 因?yàn)橐粋€(gè)真正的 DOM 元素非常龐大,擁有很多的屬性值,而實(shí)際上我們并不是全部都會(huì)用到,通常包括節(jié)點(diǎn)內(nèi)容、元素位置、樣式、節(jié)點(diǎn)的添加刪除等方法。所以,我們通過(guò)用 JS 對(duì)象表示 DOM 元素的方式,可以大大降低了比較差異的計(jì)算量。

我們來(lái)看一下 VNode 源碼,只有以下20來(lái)個(gè)屬性:

tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context fordevtools
fnScopeId: ?string; // functional scope id support

2 、比較新舊兩棵虛擬 DOM 樹(shù)的差異

虛擬 DOM 中,差異對(duì)比是很關(guān)鍵的一步,當(dāng)狀態(tài)變更的時(shí)候,重新構(gòu)造一棵新的對(duì)象樹(shù)。然后用新的樹(shù)和舊的樹(shù)進(jìn)行比較,記錄兩棵樹(shù)差異。這樣的差異需要記錄:

需要替換掉原來(lái)的節(jié)點(diǎn)
移動(dòng)、刪除、新增子節(jié)點(diǎn)
修改了節(jié)點(diǎn)的屬性
對(duì)于文本節(jié)點(diǎn)的文本內(nèi)容改變

下圖,我們對(duì)比兩棵 DOM 樹(shù),得到的差異有:

p 元素插入了一個(gè) span 元素子節(jié)點(diǎn)

原先的文本節(jié)點(diǎn)挪到了 span 元素子節(jié)點(diǎn)下面

3、應(yīng)用差異到真正的 DOM 樹(shù)

通過(guò)前面的示例,我們知道差異記錄要應(yīng)用到真正的 DOM 樹(shù)上,需要進(jìn)行一些操作,例如節(jié)點(diǎn)的替換、移動(dòng)、刪除,文本內(nèi)容的改變等。

在 Vue 中是怎么進(jìn)行 DOM Diff 呢? 簡(jiǎn)單看這段代碼感受下, 雖然代碼里很多函數(shù)沒(méi)貼出來(lái),但其實(shí)看函數(shù)名也可以大概理解都是什么作用,例如updateChildren、addVnodes、removeVnodes、setTextContent等。

// 對(duì)比差異后更新
const oldCh = oldVnode.children;
const ch = vnode.children;
if (isDef(data) && isPatchable(vnode)) {
  for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
  if (isDef((i = data.hook)) && isDef((i = i.update))) i(oldVnode, vnode);
}
if (isUndef(vnode.text)) {
  if (isDef(oldCh) && isDef(ch)) {
    if (oldCh !== ch)
      updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
  } else if (isDef(ch)) {
    if (process.env.NODE_ENV !== "production") {
      checkDuplicateKeys(ch);
    }
    if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, "");
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
  } else if (isDef(oldCh)) {
    removeVnodes(elm, oldCh, 0, oldCh.length - 1);
  } else if (isDef(oldVnode.text)) {
    nodeOps.setTextContent(elm, "");
  }
} else if (oldVnode.text !== vnode.text) {
  nodeOps.setTextContent(elm, vnode.text);
}
if (isDef(data)) {
  if (isDef((i = data.hook)) && isDef((i = i.postpatch))) i(oldVnode, vnode);
}

三、數(shù)據(jù)綁定

在 Vue 中,最基礎(chǔ)的模板語(yǔ)法是數(shù)據(jù)綁定。

例如:

<div>{{ message }}</div>

最終頁(yè)面展示內(nèi)容為<div>test</div>。那這是怎么做到的呢?

1、 數(shù)據(jù)綁定的實(shí)現(xiàn)

這種使用雙大括號(hào)來(lái)綁定變量的方式,我們稱之為數(shù)據(jù)綁定。

數(shù)據(jù)綁定的過(guò)程其實(shí)不復(fù)雜:
(1) 、解析語(yǔ)法生成 AST
(2) 、根據(jù) AST 結(jié)果生成 DOM
(3) 、將數(shù)據(jù)綁定更新至模板

這個(gè)過(guò)程是 Vue 中模板引擎在做的事情,我們來(lái)看看上面在 Vue 里的代碼片段<div></div>,我們可以通過(guò) DOM 元素捕獲,解析后獲得這樣一個(gè) AST 對(duì)象:

divObj = {
  dom: {
    type: "dom",
    ele: "div",
    nodeIndex: 0,
    children: [{ type: "text", value: "" }]
  },
  binding: [{ type: "dom", nodeIndex: 0, valueName: "message" }]
};

我們?cè)谏?DOM 的時(shí)候,添加對(duì)message的監(jiān)聽(tīng),數(shù)據(jù)更新時(shí)會(huì)找到對(duì)應(yīng)的nodeIndex更新值:

// 假設(shè)這是一個(gè)生成 DOM 的過(guò)程,包括 innerHTML 和事件監(jiān)聽(tīng)
function generateDOM(astObject) {
  const { dom, binding = [] } = astObject;
  // 生成DOM,這里假設(shè)當(dāng)前節(jié)點(diǎn)是baseDom
  baseDom.innerHTML = getDOMString(dom);
  // 對(duì)于數(shù)據(jù)綁定的,來(lái)進(jìn)行監(jiān)聽(tīng)更新
  baseDom.addEventListener("data:change", (name, value) => {
    // 尋找匹配的數(shù)據(jù)綁定
    const obj = binding.find(x => x.valueName == name);
    // 若找到值綁定的對(duì)應(yīng)節(jié)點(diǎn),則更新其值。
    if (obj) {
      baseDom.find(`[data-node-index="${obj.nodeIndex}"]`).innerHTML = value;
    }
  });
}
// 獲取DOM字符串,這里簡(jiǎn)單拼成字符串
function getDOMString(domObj) {
  // 無(wú)效對(duì)象返回''
  if (!domObj) return "";
  const { type, children = [], nodeIndex, ele, value } = domObj;
  if (type == "dom") {
    // 若有子對(duì)象,遞歸返回生成的字符串拼接
    const childString = "";
    children.forEach(x => {
      childString += getDOMString(x);
    });
    // dom對(duì)象,拼接生成對(duì)象字符串
    return `<${ele} data-node-index="${nodeIndex}">${childString}</${ele}>`;
  } else if (type == "text") {
    // 若為textNode,返回text的值
    return value;
  }
}

這樣,我們就能在message變量更新的時(shí)候,通過(guò)該變量關(guān)聯(lián)的引用,來(lái)自動(dòng)更新對(duì)應(yīng)展示的內(nèi)容。而要知道message變量什么時(shí)候進(jìn)行了改變,我們需要對(duì)數(shù)據(jù)進(jìn)行監(jiān)聽(tīng)。

2、數(shù)據(jù)更新監(jiān)聽(tīng)

加粗樣式
我們能看到,上面的簡(jiǎn)單代碼描述過(guò)程中,使用的數(shù)據(jù)監(jiān)聽(tīng)方法是用了addEventListener("data:change", Function)的方式。

在 Vue 中,數(shù)據(jù)更新的時(shí)候就執(zhí)行了模板更新、watch、computed 等一些工作,主要是依賴了Getter/Setter。而 Vue3.0 將使用Proxy的方式來(lái)進(jìn)行:

Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  // getter
  get: function reactiveGetter() {
    const value = getter ? getter.call(obj) : val;
    if (Dep.target) {
      dep.depend();
      if (childOb) {
        childOb.dep.depend();
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
    }
    return value;
  },
  // setter最終更新后會(huì)通知
  set: function reactiveSetter(newVal) {
    const value = getter ? getter.call(obj) : val;
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return;
    }
    if (process.env.NODE_ENV !== "production" && customSetter) {
      customSetter();
    }
    if (getter && !setter) return;
    if (setter) {
      setter.call(obj, newVal);
    } else {
      val = newVal;
    }
    childOb = !shallow && observe(newVal);
    dep.notify();
  }
});

Vue 中大多數(shù)能力都依賴于模板引擎,包括組件化管理、事件管理、Vue 實(shí)例、生命周期等,相信只要理解了 AST、虛擬 DOM、數(shù)據(jù)綁定相關(guān)的機(jī)制后,再去翻閱 Vue 源碼 ,了解更多的能力就不是問(wèn)題了。

以上就是Vue渲染流程步驟詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue渲染的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 一文帶你搞懂Vue3?defineModel中的雙向綁定

    一文帶你搞懂Vue3?defineModel中的雙向綁定

    隨著vue3.4版本的發(fā)布,defineModel也正式轉(zhuǎn)正了,它可以簡(jiǎn)化父子組件之間的雙向綁定,是目前官方推薦的雙向綁定實(shí)現(xiàn)方式,下面就跟隨小編一起深入了解一下defineModel的使用吧
    2024-02-02
  • vue過(guò)渡和animate.css結(jié)合使用詳解

    vue過(guò)渡和animate.css結(jié)合使用詳解

    本篇文章主要介紹了vue過(guò)渡和animate.css結(jié)合使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • vue使用Google Recaptcha驗(yàn)證的實(shí)現(xiàn)示例

    vue使用Google Recaptcha驗(yàn)證的實(shí)現(xiàn)示例

    我們最近的項(xiàng)目中需要使用谷歌機(jī)器人驗(yàn)證,所以就動(dòng)手實(shí)現(xiàn)一下,本文就來(lái)詳細(xì)的介紹一下vue Google Recaptcha驗(yàn)證,感興趣的可以了解一下
    2021-08-08
  • Vue路由vue-router用法講解

    Vue路由vue-router用法講解

    這篇文章介紹了Vue路由vue-router的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-01-01
  • 讓webpack+vue-cil項(xiàng)目不再自動(dòng)打開(kāi)瀏覽器的方法

    讓webpack+vue-cil項(xiàng)目不再自動(dòng)打開(kāi)瀏覽器的方法

    今天小編就為大家分享一篇讓webpack+vue-cil項(xiàng)目不再自動(dòng)打開(kāi)瀏覽器的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • vue項(xiàng)目及axios請(qǐng)求獲取數(shù)據(jù)方式

    vue項(xiàng)目及axios請(qǐng)求獲取數(shù)據(jù)方式

    這篇文章主要介紹了vue項(xiàng)目及axios請(qǐng)求獲取數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 關(guān)于element-ui中el-form自定義驗(yàn)證(調(diào)用后端接口)

    關(guān)于element-ui中el-form自定義驗(yàn)證(調(diào)用后端接口)

    這篇文章主要介紹了關(guān)于element-ui中el-form自定義驗(yàn)證(調(diào)用后端接口),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • vue使用element-ui按需引入時(shí)踩過(guò)的那些坑

    vue使用element-ui按需引入時(shí)踩過(guò)的那些坑

    Element-UI是基于vue實(shí)現(xiàn)的一套不依賴業(yè)務(wù)的UI組件庫(kù),提供了豐富的PC端組件,減少用戶對(duì)常用組件的封裝,降低了開(kāi)發(fā)的難易程度,下面這篇文章主要給大家介紹了關(guān)于vue使用element-ui按需引入時(shí)踩過(guò)的那些坑,需要的朋友可以參考下
    2022-05-05
  • 簡(jiǎn)述Vue中容易被忽視的知識(shí)點(diǎn)

    簡(jiǎn)述Vue中容易被忽視的知識(shí)點(diǎn)

    這篇文章主要介紹了簡(jiǎn)述Vue中容易被忽視的知識(shí)點(diǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • Vue中qs插件的使用詳解

    Vue中qs插件的使用詳解

    這篇文章主要介紹了Vue中qs插件的使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02

最新評(píng)論