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

Iframe在Vue中的狀態(tài)保持技術(shù)

 更新時間:2023年05月26日 10:46:45   作者:京東零售?陳震  
這篇文章主要為大家介紹了Iframe在Vue中的狀態(tài)保持技術(shù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

Iframe是一個歷史悠久的HTML元素,根據(jù)MDN WEB DOCS官方介紹,Iframe定義為HTML內(nèi)聯(lián)框架元素,表示嵌套的Browsing Context,它能夠?qū)⒘硪粋€HTML頁面嵌入到當(dāng)前頁面中。Iframe可以廉價實現(xiàn)跨應(yīng)用級的頁面共享,并且具有使用簡單、高兼容性、內(nèi)容隔離等優(yōu)點,因此以Iframe為核心形成了前端平臺架構(gòu)領(lǐng)域第1代技術(shù)。

眾所周知,當(dāng)Iframe在DOM中初始渲染時,會自動加載其指向的資源鏈接Url,并重置內(nèi)部的狀態(tài)。在一個典型的平臺應(yīng)用中,一個父應(yīng)用主頁面要掛載多個窗口(每一個窗口對應(yīng)一個Iframe),那么如何在切換窗口時,實現(xiàn)每一個窗口中的狀態(tài)(包括輸入狀態(tài)、錨點信息等)不丟失,也即“狀態(tài)保持”呢?

如果采用父子應(yīng)用通信來記錄窗口狀態(tài),那么改造成本是非常巨大的。答案是利用Iframe的CSS Display特性,切換窗口時,非激活狀態(tài)的窗口并不消失,僅是Display狀態(tài)變更為none,激活狀態(tài)窗口的Display狀態(tài)變更為非none。在Display狀態(tài)切換時,Iframe不會重新加載。在Vue應(yīng)用中,一行v-show指令即可替我們實現(xiàn)這一需求。

競爭機制

上述的狀態(tài)保持模型存在一個性能缺陷,即父應(yīng)用主頁面實際上要提前擺放多個Iframe窗口。即使是這些不可見的窗口,也會發(fā)出資源request請求。大量的并發(fā)請求,會導(dǎo)致頁面性能下降。(值得一提的是,Chrome最新版本已經(jīng)支持了Iframe的滾動懶加載策略,但是在此場景下,并不能改善并發(fā)請求的問題。)因此,我們需要引入資源池和競爭機制來管理多個Iframe。

引入一個容量為N的Iframe資源池來管理多開窗口,當(dāng)資源池未滿時,新激活的窗口可以直接插入至資源池中;當(dāng)資源池已滿時,資源池按照競爭策略,淘汰若干池中的窗口并丟棄,然后插入新激活的窗口至資源池中。通過調(diào)整容量N,可以限制父應(yīng)用主頁面上多開窗口的數(shù)量,從而限制并發(fā)請求數(shù)量,實現(xiàn)資源管控的目的。

Vue Patch原理探索

日前遇到了一個基于Vue應(yīng)用的Iframe狀態(tài)保持問題,在上述模型下,資源池不僅保存窗口對象,而且記錄了每個窗口的點擊激活時間。資源池使用以下競爭淘汰策略:對窗口激活時間進行先后次序排序,激活時間排序次序較前的窗口優(yōu)先被淘汰。當(dāng)資源池滿時,會偶發(fā)池中窗口狀態(tài)不能保持的問題。

在Vue中,組件是一個可復(fù)用的Vue實例,Vue 會盡可能高效地渲染元素,通常會復(fù)用已有元素而不是從頭開始渲染。組件狀態(tài)是否正確保持,依賴關(guān)鍵屬性key。基于此,首先排查了Iframe組件的key屬性。事實上,Iframe組件已經(jīng)正確分配了唯一的Uid,此種情況可以排除。

既然不是組件復(fù)用的問題,那么在Vue內(nèi)部的Diff Patch機制到底是如何運行的呢?讓我們看一下Vue 2.0的源代碼:

/**
 * 頁面首次渲染和后續(xù)更新的入口位置,也是 patch 的入口位置 
 */
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  if (!prevVnode) {
    // 老 VNode 不存在,表示首次渲染,即初始化頁面時走這里
    ……
  } else {
    // 響應(yīng)式數(shù)據(jù)更新時,即更新頁面時走這里
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
}

(1)在update生命周期下,主要執(zhí)行了vm.__patch__方法。

/** 
* vm.__patch__ 
* 1、新節(jié)點不存在,老節(jié)點存在,調(diào)用 destroy,銷毀老節(jié)點 
* 2、如果 oldVnode 是真實元素,則表示首次渲染,創(chuàng)建新節(jié)點,并插入 body,然后移除老節(jié)點 
* 3、如果 oldVnode 不是真實元素,則表示更新階段,執(zhí)行 patchVnode 
*/
function patch(oldVnode, vnode, hydrating, removeOnly) {
  …… // 1、新節(jié)點不存在,老節(jié)點存在,調(diào)用 destroy,銷毀老節(jié)點
  if (isUndef(oldVnode)) {
    …… // 2、老節(jié)點不存在,執(zhí)行創(chuàng)建新節(jié)點
  } else {
    // 判斷 oldVnode 是否為真實元素
    const isRealElement = isDef(oldVnode.nodeType)
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // 3、不是真實元素,但是老節(jié)點和新節(jié)點是同一個節(jié)點,則是更新階段,執(zhí)行 patch 更新節(jié)點
      patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
    } else {
      ……// 是真實元素,則表示初次渲染
    }
  }
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  return vnode.elm
}

(2)在__patch__方法內(nèi)部,觸發(fā)patchVnode方法。

function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
  ……
  if (isUndef(vnode.text)) {// 新節(jié)點不為文本節(jié)點
    if (isDef(oldCh) && isDef(ch)) {// 新舊節(jié)點的子節(jié)點都存在,執(zhí)行diff遞歸
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
    } else {
      ……
    }
  } else {
    ……
  }
}

(3)在patchVnode方法內(nèi)部,觸發(fā)updateChildren方法。

/**
 * diff 過程:
 *   diff 優(yōu)化:做了四種假設(shè),假設(shè)新老節(jié)點開頭結(jié)尾有相同節(jié)點的情況,一旦命中假設(shè),就避免了一次循環(huán),以提高執(zhí)行效率
 *   如果不幸沒有命中假設(shè),則執(zhí)行遍歷,從老節(jié)點中找到新開始節(jié)點
 *   找到相同節(jié)點,則執(zhí)行 patchVnode,然后將老節(jié)點移動到正確的位置
 *   如果老節(jié)點先于新節(jié)點遍歷結(jié)束,則剩余的新節(jié)點執(zhí)行新增節(jié)點操作
 *   如果新節(jié)點先于老節(jié)點遍歷結(jié)束,則剩余的老節(jié)點執(zhí)行刪除操作,移除這些老節(jié)點
 */
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  // 老節(jié)點的開始索引
  let oldStartIdx = 0
  // 新節(jié)點的開始索引
  let newStartIdx = 0
  // 老節(jié)點的結(jié)束索引
  let oldEndIdx = oldCh.length - 1
  // 第一個老節(jié)點
  let oldStartVnode = oldCh[0]
  // 最后一個老節(jié)點
  let oldEndVnode = oldCh[oldEndIdx]
  // 新節(jié)點的結(jié)束索引
  let newEndIdx = newCh.length - 1
  // 第一個新節(jié)點
  let newStartVnode = newCh[0]
  // 最后一個新節(jié)點
  let newEndVnode = newCh[newEndIdx]
  let oldKeyToIdx, idxInOld, vnodeToMove, refElm
  // 遍歷新老兩組節(jié)點,只要有一組遍歷完(開始索引超過結(jié)束索引)則跳出循環(huán)
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
      // 如果節(jié)點被移動,在當(dāng)前索引上可能不存在,檢測這種情況,如果節(jié)點不存在則調(diào)整索引
      oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
    } else if (isUndef(oldEndVnode)) {
      oldEndVnode = oldCh[--oldEndIdx]
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      // 老開始節(jié)點和新開始節(jié)點是同一個節(jié)點,執(zhí)行 patch
      patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
      // patch 結(jié)束后老開始和新開始的索引分別加 1
      oldStartVnode = oldCh[++oldStartIdx]
      newStartVnode = newCh[++newStartIdx]
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      // 老結(jié)束和新結(jié)束是同一個節(jié)點,執(zhí)行 patch
      patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
      // patch 結(jié)束后老結(jié)束和新結(jié)束的索引分別減 1
      oldEndVnode = oldCh[--oldEndIdx]
      newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
      // 老開始和新結(jié)束是同一個節(jié)點,執(zhí)行 patch
      ……
    } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
      // 老結(jié)束和新開始是同一個節(jié)點,執(zhí)行 patch
      ……
    } else {
      // 如果上面的四種假設(shè)都不成立,則通過遍歷找到新開始節(jié)點在老節(jié)點中的位置索引
      ……
        // 在老節(jié)點中找到新開始節(jié)點了
        if (sameVnode(vnodeToMove, newStartVnode)) {
          // 如果這兩個節(jié)點是同一個,則執(zhí)行 patch
          patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
          // patch 結(jié)束后將該老節(jié)點置為 undefined
          oldCh[idxInOld] = undefined
          canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
        } else {
          // 最后這種情況是,找到節(jié)點了,但是發(fā)現(xiàn)兩個節(jié)點不是同一個節(jié)點,則視為新元素,執(zhí)行創(chuàng)建
          ……
        }
      // 老節(jié)點向后移動一個
      newStartVnode = newCh[++newStartIdx]
    }
  }
  // 走到這里,說明老姐節(jié)點或者新節(jié)點被遍歷完了,執(zhí)行剩余節(jié)點的處理
  ……
}

(4)咱們終于來到了主角updateChildren。在updateChildren內(nèi)部實現(xiàn)中,使用了2套指針分別指向新舊Vnode頭尾,并向中間聚攏遞歸,以實現(xiàn)新舊數(shù)據(jù)對比刷新。

在前述資源池模型下,當(dāng)查找到新舊Iframe組件時,會執(zhí)行如下邏輯:

if (sameVnode(vnodeToMove, newStartVnode)) {
          // 如果這兩個節(jié)點是同一個,則執(zhí)行 patch
          patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
          // patch 結(jié)束后將該老節(jié)點置為 undefined
          oldCh[idxInOld] = undefined
          canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
}

看來出現(xiàn)問題的罪魁禍首是執(zhí)行了nodeOps.insertBefore。在WEB的運行環(huán)境下實際上執(zhí)行的是DOM的insertBefore API。那么我們移步來看看在DOM環(huán)境下,Iframe究竟是采取了何種刷新策略。

Iframe的狀態(tài)刷新機制

為了更清晰地看到DOM節(jié)點的變化情況,我們可以引入MutationObserver在最新版Chrome中來觀測DOM根節(jié)點。\

首先設(shè)置容器節(jié)點下有兩個子節(jié)點:<span/><iframe/>,分別執(zhí)行以下方案并記錄結(jié)果:\

對比方案A:使用insertBefore在iframe節(jié)點前再插入一個新的span節(jié)點\

對比方案B:使用insertBefore在iframe節(jié)點后再插入一個新的span節(jié)點\

對比方案C:使用insertBefore交換span和iframe節(jié)點\

對比方案D:使用insertBefore原地操作iframe自身\

其結(jié)果如下:

方案名稱Iframe是否刷新DOM節(jié)點變化
A新增一個子節(jié)點span
B新增一個子節(jié)點span
C先移除一個iframe,再插入一個iframe
D先移除一個iframe,再插入一個iframe

實驗結(jié)果顯示,對Iframe執(zhí)行insertBefore時,實際上DOM會依次執(zhí)行移除、新增節(jié)點操作,導(dǎo)致Iframe狀態(tài)刷新。

在Vuejs Issues #9473中提到了類似的問題,一種解決方案是在Vue Patch時優(yōu)先對非Iframe類型元素進行DOM操作,但是目前這個優(yōu)化策略尚未被采用,在Vue 3.0版本中也依然存在這個問題。

那么在資源池模型下,如何才能保證Iframe不執(zhí)行insertBefore呢?重新回到Vue Patch機制下,我們發(fā)現(xiàn),只有新舊Iframe在新舊Vnode列表中的相對位置保持不變時,才會只執(zhí)行patchVnode方法,而不會觸發(fā)insertBefore方法。

因此,采取的最終解決方案是,更改淘汰機制,將排序操作改為搜索操作,保證了多開窗口在Vue中的狀態(tài)保持。

以上就是Iframe在Vue中的狀態(tài)保持技術(shù)的詳細內(nèi)容,更多關(guān)于Iframe Vue狀態(tài)保持的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue中的計算屬性和axios基本使用回顧

    Vue中的計算屬性和axios基本使用回顧

    這篇文章主要介紹了Vue中的計算屬性和axios基本使用回顧,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • Vue項目使用svg圖標實踐

    Vue項目使用svg圖標實踐

    這篇文章主要介紹了Vue項目使用svg圖標實踐,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • Vue3異步數(shù)據(jù)加載組件suspense的使用方法

    Vue3異步數(shù)據(jù)加載組件suspense的使用方法

    前端開發(fā)中異步請求是非常常見的事情,比如遠程讀取圖片,調(diào)用后端接口等等,這篇文章主要給大家介紹了關(guān)于Vue3異步數(shù)據(jù)加載組件suspense的使用方法,suspense中文含義是懸念的意思,需要的朋友可以參考下
    2021-08-08
  • Vue中通過屬性綁定為元素綁定style行內(nèi)樣式的實例代碼

    Vue中通過屬性綁定為元素綁定style行內(nèi)樣式的實例代碼

    這篇文章主要介紹了Vue中通過屬性綁定為元素綁定style行內(nèi)樣式,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-04-04
  • Vue中的父子組件傳值.sync

    Vue中的父子組件傳值.sync

    這篇文章主要介紹了Vue中的父子組件傳值.sync,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Vue header組件開發(fā)詳解

    Vue header組件開發(fā)詳解

    本篇文章主要介紹了Vue header組件開發(fā)詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • vue實現(xiàn)element表格里表頭信息提示功能(推薦)

    vue實現(xiàn)element表格里表頭信息提示功能(推薦)

    小編最近接了這樣一個需求,需要在element表格操作一欄添加提示功能,下面小編給大家?guī)砹嘶趘ue實現(xiàn)element表格里表頭信息提示功能,需要的朋友參考下吧
    2019-11-11
  • 使用elementuiadmin去掉默認mock權(quán)限控制的設(shè)置

    使用elementuiadmin去掉默認mock權(quán)限控制的設(shè)置

    這篇文章主要介紹了使用elementuiadmin去掉默認mock權(quán)限控制的設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Vue.js組件props數(shù)據(jù)驗證實現(xiàn)詳解

    Vue.js組件props數(shù)據(jù)驗證實現(xiàn)詳解

    這篇文章主要為大家詳細介紹了Vue.js組件props數(shù)據(jù)驗證的實現(xiàn)方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • vue組件入門知識全梳理

    vue組件入門知識全梳理

    這篇文章主要給大家介紹了關(guān)于vue組件入門知識的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評論

晋江市| 西贡区| 武鸣县| 孝感市| 三亚市| 民勤县| 丰县| 成武县| 调兵山市| 沂源县| 通山县| 赫章县| 金堂县| 崇左市| 安图县| 买车| 克拉玛依市| 天等县| 梨树县| 建水县| 丁青县| 靖西县| 收藏| 新乡市| 兴化市| 凤冈县| 昌乐县| 新巴尔虎右旗| 安远县| 河津市| 长白| 运城市| 赣榆县| 宁津县| 南江县| 大宁县| 辉县市| 洞口县| 云阳县| 新竹县| 绥滨县|