成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Iframe在Vue中的狀態保持技術

開發 前端
Iframe可以廉價實現跨應用級的頁面共享,并且具有使用簡單、高兼容性、內容隔離等優點,因此以Iframe為核心形成了前端平臺架構領域第1代技術。

?引言

Iframe是一個歷史悠久的HTML元素,根據MDN WEB DOCS官方介紹,Iframe定義為HTML內聯框架元素,表示嵌套的Browsing Context,它能夠將另一個HTML頁面嵌入到當前頁面中。Iframe可以廉價實現跨應用級的頁面共享,并且具有使用簡單、高兼容性、內容隔離等優點,因此以Iframe為核心形成了前端平臺架構領域第1代技術。

圖片

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

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

競爭機制

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

圖片

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

Vue Patch原理探索

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

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

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

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

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

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

(2)在__patch__方法內部,觸發patchVnode方法。

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

(3)在patchVnode方法內部,觸發updateChildren方法。

/**
* diff 過程:
* diff 優化:做了四種假設,假設新老節點開頭結尾有相同節點的情況,一旦命中假設,就避免了一次循環,以提高執行效率
* 如果不幸沒有命中假設,則執行遍歷,從老節點中找到新開始節點
* 找到相同節點,則執行 patchVnode,然后將老節點移動到正確的位置
* 如果老節點先于新節點遍歷結束,則剩余的新節點執行新增節點操作
* 如果新節點先于老節點遍歷結束,則剩余的老節點執行刪除操作,移除這些老節點
*/
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
// 老節點的開始索引
let oldStartIdx = 0
// 新節點的開始索引
let newStartIdx = 0
// 老節點的結束索引
let oldEndIdx = oldCh.length - 1
// 第一個老節點
let oldStartVnode = oldCh[0]
// 最后一個老節點
let oldEndVnode = oldCh[oldEndIdx]
// 新節點的結束索引
let newEndIdx = newCh.length - 1
// 第一個新節點
let newStartVnode = newCh[0]
// 最后一個新節點
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm


// 遍歷新老兩組節點,只要有一組遍歷完(開始索引超過結束索引)則跳出循環
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
// 如果節點被移動,在當前索引上可能不存在,檢測這種情況,如果節點不存在則調整索引
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
// 老開始節點和新開始節點是同一個節點,執行 patch
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
// patch 結束后老開始和新開始的索引分別加 1
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
// 老結束和新結束是同一個節點,執行 patch
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
// patch 結束后老結束和新結束的索引分別減 1
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
// 老開始和新結束是同一個節點,執行 patch
……
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
// 老結束和新開始是同一個節點,執行 patch
……
} else {
// 在老節點中找到新開始節點了
if (sameVnode(vnodeToMove, newStartVnode)) {
// 如果這兩個節點是同一個,則執行 patch
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
// patch 結束后將該老節點置為 undefined
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
……
}
// 老節點向后移動一個
newStartVnode = newCh[++newStartIdx]
}
}
// 走到這里,說明老姐節點或者新節點被遍歷完了
……
}

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

圖片

在前述資源池模型下,當查找到新舊Iframe組件時,會執行如下邏輯:

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

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

Iframe的狀態刷新機制

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

首先設置容器節點下有兩個子節點:<span/>和<iframe/>,分別執行以下方案并記錄結果:

  • 對比方案A:使用insertBefore在iframe節點前再插入一個新的span節點
  • 對比方案B:使用insertBefore在iframe節點后再插入一個新的span節點
  • 對比方案C:使用insertBefore交換span和iframe節點
  • 對比方案D:使用insertBefore原地操作iframe自身

其結果如下:

圖片

實驗結果顯示,對Iframe執行insertBefore時,實際上DOM會依次執行移除、新增節點操作,導致Iframe狀態刷新。

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

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

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

責任編輯:未麗燕 來源: 京東零售技術
相關推薦

2010-10-08 10:15:34

IFrameJS控件

2013-04-03 09:51:05

創業快樂創業

2010-02-24 13:42:55

WCF PreSess

2022-02-15 15:34:10

大數據天體系統技術

2011-11-30 07:38:07

存儲虛擬化

2020-06-02 09:06:31

VueTransition前端

2021-08-28 10:06:29

VueJavascript應用

2016-12-01 13:44:19

iosandroid

2010-09-30 15:51:56

iframeJS

2009-06-11 13:52:25

協同軟件Java

2022-10-14 16:18:40

MobileNetAndroid端模型訓練

2009-11-03 11:03:00

CDN接入技術

2011-11-21 18:19:20

Web iMC

2012-05-11 11:47:55

存儲虛擬化

2020-01-17 11:20:30

5G物聯網智慧城市

2020-04-24 22:05:44

冠狀病毒物聯網IOT

2018-03-19 19:00:54

2024-10-31 15:16:35

2010-06-09 14:21:05

UML狀態圖

2023-04-26 15:17:33

Vue 3開發前端
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美精品在线一区二区三区 | 在线播放第一页 | 一区二区三区回区在观看免费视频 | 在线看av的网址 | 一级片aaa| 午夜久久久 | 久免费视频 | 美女视频黄色的 | 国产一区二区成人 | 99色综合| 丁香色婷婷 | 免费v片| 国产亚洲一区二区三区 | 日日干天天干 | 国产成人综合在线 | 成人二区| 久草视频网站 | 九色视频网站 | 成人免费xxxxx在线视频 | 一级做受毛片免费大片 | 精品国产伦一区二区三区观看方式 | 午夜久久av | 亚洲伊人久久综合 | 国产在线不卡 | 日韩一区二区三区视频 | 久草视频在线看 | 99久久精品国产一区二区三区 | 亚洲成人网在线播放 | 亚洲97| av黄色在线| 视频一区二区中文字幕 | 国产一极毛片 | 日韩伦理一区二区 | 亚洲免费一区二区 | 亚洲天堂精品一区 | 中文字幕1区| 欧美黑人一区二区三区 | 91精品国产色综合久久不卡蜜臀 | 午夜合集 | 免费看一级毛片 | 欧美成人a∨高清免费观看 老司机午夜性大片 |