VUE3主要在哪些方面做了性能提升?


概要

Vue (讀音 /vjuː/,類似於 view) 是一套用於構建用戶界面的漸進式框架。vue2版本階段已經證明了它的易用性和流行性,說明它已經足夠優秀在構建前端應用領域,而vue3的推出更是將性能提升做了最大的優化,更加易用、靈活、高效,未來是屬於vue3的時代,因此深入了解vue3相對vue2在哪些方面做了性能提升,怎么提升性能的是非常有必要的。

編譯階優化段

在Vue2中,每個組件實例都對應一個 watcher 實例,它會在組件渲染的過程中把用到的數據property記錄為依賴,當依賴發生改變,觸發setter,則會通知watcher,從而使關聯的組件重新渲染

假如一個vue組件有如下模板結構:

<template>
    <div id="content">
        <p class="text">靜態文本</p>
        <p class="text">靜態文本</p>
        <p class="text">{{ message }}</p>
        <p class="text">靜態文本</p>
        ...
        <p class="text">靜態文本</p>
    </div>
</template>

可以看到,組件內部只有一個動態節點,剩余一堆都是靜態節點,所以這里很多 diff 和遍歷其實都是不需要的,會造成性能浪費。因此,Vue3在編譯階段,做了進一步優化。主要有如下:

  • diff算法優化
  • 靜態提升
  • 預字符串化
  • 緩存事件處理函數
  • SSR優化

diff算法優化

vue3在diff算法中相比vue2增加了靜態標記
關於這個靜態標記,其作用是為了會發生變化的地方添加一個flag標記,下次發生變化的時候直接找該地方進行比較,如下圖

靜態類型如下所示

export const enum PatchFlags {
  TEXT = 1,// 動態的文本節點
  CLASS = 1 << 1,  // 2 動態的 class
  STYLE = 1 << 2,  // 4 動態的 style
  PROPS = 1 << 3,  // 8 動態屬性,不包括類名和樣式
  FULL_PROPS = 1 << 4,  // 16 動態 key,當 key 變化時需要完整的 diff 算法做比較
  HYDRATE_EVENTS = 1 << 5,  // 32 表示帶有事件監聽器的節點
  STABLE_FRAGMENT = 1 << 6,   // 64 一個不會改變子節點順序的 Fragment
  KEYED_FRAGMENT = 1 << 7, // 128 帶有 key 屬性的 Fragment
  UNKEYED_FRAGMENT = 1 << 8, // 256 子節點沒有 key 的 Fragment
  NEED_PATCH = 1 << 9,   // 512
  DYNAMIC_SLOTS = 1 << 10,  // 動態 solt
  HOISTED = -1,  // 特殊標志是負整數表示永遠不會用作 diff
  BAIL = -2 // 一個特殊的標志,指代差異算法
}

靜態提升

Vue3中對不參與更新的元素,會做靜態提升,只會被創建一次,在渲染時直接復用
這樣就免去了重復的創建節點,大型應用會受益於這個改動,免去了重復的創建操作,優化了運行時候的內存占用

<span>你好</span>
<div>{{ message }}</div>

沒有做靜態提升之前

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("span", null, "你好"),
    _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ], 64 /* STABLE_FRAGMENT */))
}

做了靜態提升之后

const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)
 
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _hoisted_1,
    _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ], 64 /* STABLE_FRAGMENT */))
}
 
// Check the console for the AST

靜態內容_hoisted_1被放置在render 函數外,每次渲染的時候只要取 _hoisted_1 即可
同時 _hoisted_1 被打上了 PatchFlag ,靜態標記值為 -1 ,特殊標志是負整數表示永遠不會用於 Diff

預字符串化

在平時vue開發過程中,組件當中沒有特別多的動態元素,大多都是靜態元素。比如:

  <div class="menu-bar-container">
    <div class="logo">
      <h1>法醫</h1>
    </div>
    <ul class="nav">
      <li><a href="">menu</a></li>
      <li><a href="">menu</a></li>
      <li><a href="">menu</a></li>
      <li><a href="">menu</a></li>
      <li><a href="">menu</a></li>
    </ul>
  </div>
  <div class="user">
    <span>{{user.name}}</span>
  </div>

在這個組件中,除了span元素是動態元素之外,其余都是靜態節點,一般可以說是動靜比,動態內容 / 靜態內容,比例越小,靜態內容越多,比例越大,動態內容越多,vue3的編譯器它會非常智能地發現這一點,當編譯器遇到大量連續的靜態內容,會直接將它編譯為一個普通字符串節點,因為它知道這些內容永遠不會變化,都是靜態節點。

注意:必須是大量連續的靜態內容才可以預字符串化哦,切記!目前是連續20個靜態節點才會預字符串化

然而在vue2中,每個元素都會變成虛擬節點,一大堆的虛擬節點😱,這些全都是靜態節點,在vue3中它會智能地發現這一點。如下圖所示,我們可以很明顯的感受到vue3的巨大性能提升

緩存事件處理函數

比如存在如下事件處理函數

<button @click="count++">plus</button>

對比vue2和vue3的處理方式

// vue2處理方式
render(ctx){
    return createVNode("button",{
        onclick:function($event){
            ctx.count++;
        }
    })
}

//vue3 處理方式
render(ctx,_cache){
    return createVNode("button",{
        onclick:cache[0] || (cache[0] =>($event) =>(ctx.count++))
    })
}

在vue2中創建一個虛擬節點button,屬性里面多了一個事件onclick,內容就是count++。
在vue3中會認為這里的事件處理是不會變化的,不是說這次渲染是事件函數,下次就變成別的,於是vue3會智能地發現這一點,會做緩存處理,它首先會看一看緩存里面有沒有這個事件函數,有的話直接返回,沒有的話就直接賦值為一個count++函數,保證事件處理函數只生成一次。

SSR優化

當靜態內容大到一定量級時候,會用createStaticVNode方法在客戶端去生成一個static node,這些靜態node,會被直接innerHtml,就不需要創建對象,然后根據對象渲染。

編譯前

div>
 <div>
  <span>你好</span>
 </div>
 ...  // 很多個靜態屬性
 <div>
  <span>{{ message }}</span>
 </div>
</div>

編譯后

import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "@vue/server-renderer"
 
export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
  const _cssVars = { style: { color: _ctx.color }}
  _push(`<div${
    _ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
  }><div><span>你好</span>...<div><span>你好</span><div><span>${
    _ssrInterpolate(_ctx.message)
  }</span></div></div>`)
}

源碼體積有優化

與Vue2相比較,Vue3整體體積變小了,移除了一些比較冷門的feature:如 keyCode 支持作為 v-on 的修飾符、on、off 和 $once 實例方法、filter過濾、內聯模板等。
tree-shaking 依賴 ES2015 模塊語法的靜態結構(即 import 和 export),通過編譯階段的靜態分析,找到沒有引入的模塊並打上標記。任何一個函數,如ref、reavtived、computed等,僅僅在用到的時候才打包,沒用到的模塊都被搖掉,打包的整體體積變小。

import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
    setup(props, context) {
        const age = ref(18)
 
        let state = reactive({
            name: 'test'
        })
 
        const readOnlyAge = computed(() => age.value++) // 19
 
        return {
            age,
            state,
            readOnlyAge
        }
    }
});

響應式實現優化

改用proxy api做數據劫持

  • Vue.js 2.x 內部是通過 Object.defineProperty 這個 API 去劫持數據的 getter 和 setter 來實現響應式的。這個 API 有一些缺陷,它必須預先知道要攔截的 key 是什么,所以它並不能檢測對象屬性的添加和刪除。

  • Vue.js 3.0 使用了 Proxy API 做數據劫持,它劫持的是整個對象,自然對於對象的屬性的增加和刪除都能檢測到。

響應式是惰性的

  • 在 Vue.js 2.x 中,對於一個深層屬性嵌套的對象,要劫持它內部深層次的變化,就需要遞歸遍歷這個對象,執行 Object.defineProperty 把每一層對象數據都變成響應式的,這無疑會有很大的性能消耗。

  • 在 Vue.js 3.0 中,使用 Proxy API 並不能監聽到對象內部深層次的屬性變化,因此它的處理方式是在 getter 中去遞歸響應式,這樣的好處是真正訪問到的內部屬性才會變成響應式,簡單的可以說是按需實現響應式,就沒有那么大的性能消耗。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM