<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          淺析 Vue.js 中那些空間換時間的操作(好文收藏?。?/h1>

          共 5740字,需瀏覽 12分鐘

           ·

          2021-05-16 03:43

          本期的問題:在 Vue.js 模板的編譯過程中,我們已經知道靜態(tài)提升的好處:針對靜態(tài)節(jié)點不用每次在 render 階段都執(zhí)行一次 createVNode 創(chuàng)建 vnode 對象。但它有沒有成本呢?為什么?

          在回答問題前,我們簡單回顧一下什么是靜態(tài)提升,假設我們有如下模板:

          <p>hello {{ msg }}</p>
          <p>static</p>

          在開啟 hoistStatic 編譯配置的情況下最終編譯結果如下:

          import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"

          const _hoisted_1 = /*#__PURE__*/_createVNode("p"null"static"-1 /* HOISTED */)

          export function render(_ctx, _cache, $props, $setup, $data, $options{
            return (_openBlock(), _createBlock(_Fragment, null, [
              _createVNode("p"null"hello " + _toDisplayString(_ctx.msg), 1 /* TEXT */),
              _hoisted_1
            ], 64 /* STABLE_FRAGMENT */))
          }

          我們發(fā)現(xiàn)靜態(tài)節(jié)點 <p>static</p> 編譯生成 vnode 的過程被提取到 render 函數(shù)外面了,然后在 render 函數(shù)內部就可以直接拿到靜態(tài)節(jié)點的編譯結果 _hoisted_1

          之所以可以這么做,是因為靜態(tài)節(jié)點是不會改變的,所以它編譯生成的 vnode 也不會改變,而動態(tài)節(jié)點是變化的,必須在每次 render 的時候動態(tài)創(chuàng)建,它的 vnode生成過程就不能提取到外面。

          顯然,這樣做的好處是由于 render 函數(shù)在每次組件重新渲染的時候都會執(zhí)行,而針對靜態(tài)節(jié)點,創(chuàng)建 vnode 的過程只執(zhí)行一次,相當于提升了 render 的性能。

          但是這么做也是有成本的,創(chuàng)建的 hoisted_1 vnode 對象始終會在內存中占用,并不會在每次 render 函數(shù)執(zhí)行后釋放。

          其實,這就是典型的空間換時間的做法,在絕大部分的場景,性能好意味著更好的用戶體驗,而犧牲那一點內存空間完全是可接受的,對于用戶也是無感知的,所以空間換時間是常見的一種優(yōu)化手段。

          在整個 Vue.js 源碼內部,經??梢砸姷竭@種空間換時間的操作,接下來我們就來看幾個 Vue.js 中常見的空間換時間的操作。

          Vue.js 中常見的空間換時間操作

          • reactive API

          Vue.js 3.0 使用 Proxy API 把對象變成響應式,一旦某個對象經過 reactive API 變成響應式對象后,會把響應式結果存儲起來,大致如下:

          function createReactiveObject(
            target: Target,
            isReadonly: boolean,
            baseHandlers: ProxyHandler<any>,
            collectionHandlers: ProxyHandler<any>
          {
            // ...
            const proxyMap = isReadonly ? readonlyMap : reactiveMap
            const existingProxy = proxyMap.get(target)
            if (existingProxy) {
              return existingProxy
            }
            // ...
            const proxy = new Proxy(
              target,
              targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
            )
            proxyMap.set(target, proxy)
            return proxy
          }

          在整個響應式模塊的內部,使用了 WeakMap 的數(shù)據結構存儲響應式結果,它的 key 是原始的 Target 對象,值是 Proxy 對象。

          export const reactiveMap = new WeakMap<Target, any>()
          export const readonlyMap = new WeakMap<Target, any>()

          這樣一來,同樣的對象如果再次執(zhí)行 reactive,則從緩存的 proxyMap 中直接拿到對應的響應式值并返回。


          • KeepAlive 組件

          整個 KeepAlive 組件的設計,本質上就是空間換時間。在 KeepAlive 組件內部,在組件渲染掛載和更新前都會緩存組件的渲染子樹 subTree,如下:

          const cacheSubtree = () => {
            if (pendingCacheKey != null) {
              cache.set(pendingCacheKey, getInnerChild(instance.subTree))
            }
          }
          onMounted(cacheSubtree)
          onUpdated(cacheSubtree)

          這個子樹一旦被緩存了,在下一次渲染的時候就可以直接從緩存中拿到子樹 vnode 以及對應的 DOM 元素來渲染。

          KeepAlive 具體實現(xiàn)細節(jié)我在課程中有專門的一小節(jié)課說明,這里就不多贅述了。

          • 工具函數(shù) cacheStringFunction

          Vue.js 源碼內部的一些工具函數(shù)的實現(xiàn),也利用了空間換時間的思想,比如 cacheStringFunction 函數(shù),如下:

          const cacheStringFunction = <T extends (str: string) => string>(fn: T): T => {
            const cache: Record<stringstring> = Object.create(null)
            return ((str: string) => {
              const hit = cache[str]
              return hit || (cache[str] = fn(str))
            }
          as any
          }

          cacheStringFunction 函數(shù)的實現(xiàn)很簡單,內部定義了 cache 變量做緩存,并返回了一個新的函數(shù)。

          在新函數(shù)的內部,先嘗試中從緩存中拿數(shù)據,如果不存在則執(zhí)行函數(shù) fn,并把 fn 的返回結果用 cache 緩存,這樣下一次就可以命中緩存了。

          我們來看看 cacheStringFunction 的幾個應用場景:

          const camelizeRE = /-(\w)/g

          export const camelize = cacheStringFunction(
            (str: string): string => {
              return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
            }
          )

          const hyphenateRE = /\B([A-Z])/g

          export const hyphenate = cacheStringFunction((str: string) =>
            str.replace(hyphenateRE, '-$1').toLowerCase()
          )

          export const capitalize = cacheStringFunction(
            (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
          )

          可以看到,這些字符串變形的相關函數(shù)都使用了 cacheStringFunction,這樣就保證了同樣的字符串,在調用某個字符串變形函數(shù)后會把結果緩存,然后同一字符串再次執(zhí)行該函數(shù)的時候就能從緩存拿結果了。

          注意,Vue.js 內部之所以給這些字符串變形函數(shù)設計緩存,是因為它們的緩存命中率高,如果緩存命中率低的話,這類空間換時間的緩存設計就可能變成負優(yōu)化了。

          會造成內存泄漏嗎

          看到這里,你可能會有疑惑,空間換時間的基本操作都是通過緩存的方式,那這會造成內存泄漏嗎?

          回答這個問題前,你先要明白什么是內存泄漏:

          內存泄漏(Memory leak)是在計算機科學中,由于疏忽或錯誤造成程序未能釋放已經不再使用的內存。內存泄漏并非指內存在物理上的消失,而是應用程序分配某段內存后,由于設計錯誤,導致在釋放該段內存之前就失去了對該段內存的控制,從而造成了內存的浪費。

          簡單點說,內存泄漏就是那些你已經用不到的內存空間,由于沒有釋放而產生的內存浪費。

          而我們空間換時間所設計的緩存,都是需要用到的內存空間,所以算是內存占用,并非內存泄漏。

          關于使用 Vue.js 開發(fā)工作中可能會造成內存泄漏的場景,我在前幾篇文章中提到了,如果你還不了解,建議你去看一看。

          總結

          綜上,我們了解到 Vue.js 在編譯過程中使用靜態(tài)提升并非無成本,但是總體來看收益大于成本。此外,我們也了解 Vue.js 中一些空間換時間的操作,我希望你能學會這個優(yōu)化思想并把它運用到自己平時的工作中。

          我出這個題主要是希望你能做到以下兩點:

          1. 學習 Vue.js 編譯過程中的一些優(yōu)化操作,并能思考它為什么能起到優(yōu)化效果。

          2. 了解優(yōu)化背后可能會造成的成本,學會評估成本和收益。

          要記住,分析和思考的過程遠比答案重要。


          最后



          如果你覺得這篇內容對你挺有啟發(fā),我想邀請你幫我三個小忙:

          1. 點個「在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)

          2. 歡迎加我微信「 sherlocked_93 」拉你進技術群,長期交流學習...

          3. 關注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。


          點個在看支持我吧,轉發(fā)就更好了



          瀏覽 22
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報

          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲性爱二区 | 日韩欧美高清视频 | 91人妻天天操天天干 | 永久免费无码中文字幕 | 大黑鸡巴干中国美女大肥臀视频 |