亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

vue中Virtual Dom實現snabbdom解密的示例分析

發布時間:2021-07-07 14:04:09 來源:億速云 閱讀:143 作者:小新 欄目:web開發

這篇文章給大家分享的是有關vue中Virtual Dom實現snabbdom解密的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

vue在官方文檔中提到與react的渲染性能對比中,因為其使用了snabbdom而有更優異的性能。

JavaScript 開銷直接與求算必要 DOM 操作的機制相關。盡管 Vue 和 React 都使用了 Virtual Dom 實現這一點,但 Vue 的 Virtual Dom 實現(復刻自 snabbdom)是更加輕量化的,因此也就比 React 的實現更高效。

看到火到不行的國產前端框架vue也在用別人的 Virtual Dom開源方案,是不是很好奇snabbdom有何強大之處呢?不過正式解密snabbdom之前,先簡單介紹下Virtual Dom。

什么是Virtual Dom

Virtual Dom可以看做一棵模擬了DOM樹的JavaScript樹,其主要是通過vnode,實現一個無狀態的組件,當組件狀態發生更新時,然后觸發Virtual Dom數據的變化,然后通過Virtual Dom和真實DOM的比對,再對真實DOM更新。可以簡單認為Virtual Dom是真實DOM的緩存。

為什么用Virtual Dom

我們知道,當我們希望實現一個具有復雜狀態的界面時,如果我們在每個可能發生變化的組件上都綁定事件,綁定字段數據,那么很快由于狀態太多,我們需要維護的事件和字段將會越來越多,代碼也會越來越復雜,于是,我們想我們可不可以將視圖和狀態分開來,只要視圖發生變化,對應狀態也發生變化,然后狀態變化,我們再重繪整個視圖就好了。

這樣的想法雖好,但是代價太高了,于是我們又想,能不能只更新狀態發生變化的視圖?于是Virtual Dom應運而生,狀態變化先反饋到Virtual Dom上,Virtual Dom在找到最小更新視圖,最后批量更新到真實DOM上,從而達到性能的提升。

除此之外,從移植性上看,Virtual Dom還對真實dom做了一次抽象,這意味著Virtual Dom對應的可以不是瀏覽器的DOM,而是不同設備的組件,極大的方便了多平臺的使用。如果是要實現前后端同構直出方案,使用Virtual Dom的框架實現起來是比較簡單的,因為在服務端的Virtual Dom跟瀏覽器DOM接口并沒有綁定關系。

基于Virtual DOM 的數據更新與UI同步機制:

vue中Virtual Dom實現snabbdom解密的示例分析

初始渲染時,首先將數據渲染為 Virtual DOM,然后由 Virtual DOM 生成 DOM。

vue中Virtual Dom實現snabbdom解密的示例分析

數據更新時,渲染得到新的 Virtual DOM,與上一次得到的 Virtual DOM 進行 diff,得到所有需要在 DOM 上進行的變更,然后在 patch 過程中應用到 DOM 上實現UI的同步更新。

Virtual DOM 作為數據結構,需要能準確地轉換為真實 DOM,并且方便進行對比。

介紹完Virtual DOM,我們應該對snabbdom的功用有個認識了,下面具體解剖下snabbdom這只“小麻雀”。

snabbdom

vnode

DOM 通常被視為一棵樹,元素則是這棵樹上的節點(node),而 Virtual DOM 的基礎,就是 Virtual Node 了。

Snabbdom 的 Virtual Node 則是純數據對象,通過 vnode 模塊來創建,對象屬性包括:

sel
data
children
text
elm
key

可以看到 Virtual Node 用于創建真實節點的數據包括:

元素類型
元素屬性
元素的子節點

源碼:

//VNode函數,用于將輸入轉化成VNode
 /**
 *
 * @param sel 選擇器
 * @param data 綁定的數據
 * @param children 子節點數組
 * @param text 當前text節點內容
 * @param elm 對真實dom element的引用
 * @returns {{sel: *, data: *, children: *, text: *, elm: *, key: undefined}}
 */
function vnode(sel, data, children, text, elm) {

 var key = data === undefined ? undefined : data.key;
 return { sel: sel, data: data, children: children,
 text: text, elm: elm, key: key };
}

snabbdom并沒有直接暴露vnode對象給我們用,而是使用h包裝器,h的主要功能是處理參數:

h(sel,[data],[children],[text]) => vnode

從snabbdom的typescript的源碼可以看出,其實就是這幾種函數重載:

export function h(sel: string): VNode; 
export function h(sel: string, data: VNodeData): VNode; 
export function h(sel: string, text: string): VNode; 
export function h(sel: string, children: Array<VNode | undefined | null>): VNode; 
export function h(sel: string, data: VNodeData, text: string): VNode; 
export function h(sel: string, data: VNodeData, children: Array<VNode | undefined | null>): VNode;

patch

創建vnode后,接下來就是調用patch方法將Virtual Dom渲染成真實DOM了。patch是snabbdom的init函數返回的。
snabbdom.init傳入modules數組,module用來擴展snabbdom創建復雜dom的能力。

不多說了直接上patch的源碼:

return function patch(oldVnode, vnode) {
 var i, elm, parent;
 //記錄被插入的vnode隊列,用于批觸發insert
 var insertedVnodeQueue = [];
 //調用全局pre鉤子
 for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
 //如果oldvnode是dom節點,轉化為oldvnode
 if (isUndef(oldVnode.sel)) {
 oldVnode = emptyNodeAt(oldVnode);
 }
 //如果oldvnode與vnode相似,進行更新
 if (sameVnode(oldVnode, vnode)) {
 patchVnode(oldVnode, vnode, insertedVnodeQueue);
 } else {
 //否則,將vnode插入,并將oldvnode從其父節點上直接刪除
 elm = oldVnode.elm;
 parent = api.parentNode(elm);

 createElm(vnode, insertedVnodeQueue);

 if (parent !== null) {
 api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
 removeVnodes(parent, [oldVnode], 0, 0);
 }
 }
 //插入完后,調用被插入的vnode的insert鉤子
 for (i = 0; i < insertedVnodeQueue.length; ++i) {
 insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);
 }
 //然后調用全局下的post鉤子
 for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
 //返回vnode用作下次patch的oldvnode
 return vnode;
 };

先判斷新舊虛擬dom是否是相同層級vnode,是才執行patchVnode,否則創建新dom刪除舊dom,判斷是否相同vnode比較簡單:

function sameVnode(vnode1, vnode2) {
 //判斷key值和選擇器
 return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}

patch方法里面實現了snabbdom 作為一個高效virtual dom庫的法寶—高效的diff算法,可以用一張圖示意:

vue中Virtual Dom實現snabbdom解密的示例分析

diff算法的核心是比較只會在同層級進行, 不會跨層級比較。而不是逐層逐層搜索遍歷的方式,時間復雜度將會達到 O(n^3)的級別,代價非常高,而只比較同層級的方式時間復雜度可以降低到O(n)。

patchVnode函數的主要作用是以打補丁的方式去更新dom樹。

function patchVnode(oldVnode, vnode, insertedVnodeQueue) {
 var i, hook;
 //在patch之前,先調用vnode.data的prepatch鉤子
 if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
 i(oldVnode, vnode);
 }
 var elm = vnode.elm = oldVnode.elm, oldCh = oldVnode.children, ch = vnode.children;
 //如果oldvnode和vnode的引用相同,說明沒發生任何變化直接返回,避免性能浪費
 if (oldVnode === vnode) return;
 //如果oldvnode和vnode不同,說明vnode有更新
 //如果vnode和oldvnode不相似則直接用vnode引用的DOM節點去替代oldvnode引用的舊節點
 if (!sameVnode(oldVnode, vnode)) {
 var parentElm = api.parentNode(oldVnode.elm);
 elm = createElm(vnode, insertedVnodeQueue);
 api.insertBefore(parentElm, elm, oldVnode.elm);
 removeVnodes(parentElm, [oldVnode], 0, 0);
 return;
 }
 //如果vnode和oldvnode相似,那么我們要對oldvnode本身進行更新
 if (isDef(vnode.data)) {
 //首先調用全局的update鉤子,對vnode.elm本身屬性進行更新
 for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
 //然后調用vnode.data里面的update鉤子,再次對vnode.elm更新
 i = vnode.data.hook;
 if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode);
 }
 //如果vnode不是text節點
 if (isUndef(vnode.text)) {
 //如果vnode和oldVnode都有子節點
 if (isDef(oldCh) && isDef(ch)) {
 //當Vnode和oldvnode的子節點不同時,調用updatechilren函數,diff子節點
 if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
 }
 //如果vnode有子節點,oldvnode沒子節點
 else if (isDef(ch)) {
 //oldvnode是text節點,則將elm的text清除
 if (isDef(oldVnode.text)) api.setTextContent(elm, '');
 //并添加vnode的children
 addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
 }
 //如果oldvnode有children,而vnode沒children,則移除elm的children
 else if (isDef(oldCh)) {
 removeVnodes(elm, oldCh, 0, oldCh.length - 1);
 }
 //如果vnode和oldvnode都沒chidlren,且vnode沒text,則刪除oldvnode的text
 else if (isDef(oldVnode.text)) {
 api.setTextContent(elm, '');
 }
 }

 //如果oldvnode的text和vnode的text不同,則更新為vnode的text
 else if (oldVnode.text !== vnode.text) {
 api.setTextContent(elm, vnode.text);
 }
 //patch完,觸發postpatch鉤子
 if (isDef(hook) && isDef(i = hook.postpatch)) {
 i(oldVnode, vnode);
 }
 }

patchVnode將新舊虛擬DOM分為幾種情況,執行替換textContent還是updateChildren。

updateChildren是實現diff算法的主要地方:

function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
 var oldStartIdx = 0, newStartIdx = 0;
 var oldEndIdx = oldCh.length - 1;
 var oldStartVnode = oldCh[0];
 var oldEndVnode = oldCh[oldEndIdx];
 var newEndIdx = newCh.length - 1;
 var newStartVnode = newCh[0];
 var newEndVnode = newCh[newEndIdx];
 var oldKeyToIdx;
 var idxInOld;
 var elmToMove;
 var before;
 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
 if (oldStartVnode == null) {
 oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
 }
 else if (oldEndVnode == null) {
 oldEndVnode = oldCh[--oldEndIdx];
 }
 else if (newStartVnode == null) {
 newStartVnode = newCh[++newStartIdx];
 }
 else if (newEndVnode == null) {
 newEndVnode = newCh[--newEndIdx];
 }
 else if (sameVnode(oldStartVnode, newStartVnode)) {
 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
 oldStartVnode = oldCh[++oldStartIdx];
 newStartVnode = newCh[++newStartIdx];
 }
 else if (sameVnode(oldEndVnode, newEndVnode)) {
 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
 oldEndVnode = oldCh[--oldEndIdx];
 newEndVnode = newCh[--newEndIdx];
 }
 else if (sameVnode(oldStartVnode, newEndVnode)) {
 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
 api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
 oldStartVnode = oldCh[++oldStartIdx];
 newEndVnode = newCh[--newEndIdx];
 }
 else if (sameVnode(oldEndVnode, newStartVnode)) {
 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
 api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
 oldEndVnode = oldCh[--oldEndIdx];
 newStartVnode = newCh[++newStartIdx];
 }
 else {
 if (oldKeyToIdx === undefined) {
  oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
 }
 idxInOld = oldKeyToIdx[newStartVnode.key];
 if (isUndef(idxInOld)) {
  api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
  newStartVnode = newCh[++newStartIdx];
 }
 else {
  elmToMove = oldCh[idxInOld];
  if (elmToMove.sel !== newStartVnode.sel) {
  api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
  }
  else {
  patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
  oldCh[idxInOld] = undefined;
  api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
  }
  newStartVnode = newCh[++newStartIdx];
 }
 }
 }
 if (oldStartIdx > oldEndIdx) {
 before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
 addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
 }
 else if (newStartIdx > newEndIdx) {
 removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
 }
 }

updateChildren的代碼比較有難度,借助幾張圖比較好理解些:

vue中Virtual Dom實現snabbdom解密的示例分析

過程可以概括為:oldCh和newCh各有兩個頭尾的變量StartIdx和EndIdx,它們的2個變量相互比較,一共有4種比較方式。如果4種比較都沒匹配,如果設置了key,就會用key進行比較,在比較的過程中,變量會往中間靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一個已經遍歷完了,就會結束比較。

具體的diff分析:
對于與sameVnode(oldStartVnode, newStartVnode)和sameVnode(oldEndVnode,newEndVnode)為true的情況,不需要對dom進行移動。

有3種需要dom操作的情況:

1.當oldStartVnode,newEndVnode相同層級時,說明oldStartVnode.el跑到oldEndVnode.el的后邊了。

vue中Virtual Dom實現snabbdom解密的示例分析

2.當oldEndVnode,newStartVnode相同層級時,說明oldEndVnode.el跑到了newStartVnode.el的前邊。

vue中Virtual Dom實現snabbdom解密的示例分析

3.newCh中的節點oldCh里沒有,將新節點插入到oldStartVnode.el的前邊。

vue中Virtual Dom實現snabbdom解密的示例分析

在結束時,分為兩種情況:

1.oldStartIdx > oldEndIdx,可以認為oldCh先遍歷完。當然也有可能newCh此時也正好完成了遍歷,統一都歸為此類。此時newStartIdx和newEndIdx之間的vnode是新增的,調用addVnodes,把他們全部插進before的后邊,before很多時候是為null的。addVnodes調用的是insertBefore操作dom節點,我們看看insertBefore的文檔:parentElement.insertBefore(newElement, referenceElement)如果referenceElement為null則newElement將被插入到子節點的末尾。如果newElement已經在DOM樹中,newElement首先會從DOM樹中移除。所以before為null,newElement將被插入到子節點的末尾。

vue中Virtual Dom實現snabbdom解密的示例分析

2.newStartIdx > newEndIdx,可以認為newCh先遍歷完。此時oldStartIdx和oldEndIdx之間的vnode在新的子節點里已經不存在了,調用removeVnodes將它們從dom里刪除。

vue中Virtual Dom實現snabbdom解密的示例分析

hook

shabbdom主要流程的代碼在上面就介紹完畢了,在上面的代碼中可能看不出來如果要創建比較復雜的dom,比如有attribute、props、eventlistener的dom怎么辦?奧秘就在與shabbdom在各個主要的環節提供了鉤子。鉤子方法中可以執行擴展模塊,attribute、props、eventlistener等可以通過擴展模塊實現。

在源碼中可以看到hook是在snabbdom初始化的時候注冊的。

var hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post'];
var h_1 = require("./h");
exports.h = h_1.h;
var thunk_1 = require("./thunk");
exports.thunk = thunk_1.thunk;
function init(modules, domApi) {
 var i, j, cbs = {};
 var api = domApi !== undefined ? domApi : htmldomapi_1.default;
 for (i = 0; i < hooks.length; ++i) {
 cbs[hooks[i]] = [];
 for (j = 0; j < modules.length; ++j) {
 var hook = modules[j][hooks[i]];
 if (hook !== undefined) {
 cbs[hooks[i]].push(hook);
 }
 }
 }

snabbdom在全局下有6種類型的鉤子,觸發這些鉤子時,會調用對應的函數對節點的狀態進行更改首先我們來看看有哪些鉤子以及它們觸發的時間:

vue中Virtual Dom實現snabbdom解密的示例分析

比如在patch的代碼中可以看到調用了pre鉤子

return function patch(oldVnode, vnode) {
 var i, elm, parent;
 var insertedVnodeQueue = [];
 for (i = 0; i < cbs.pre.length; ++i)
 cbs.pre[i]();
 if (!isVnode(oldVnode)) {
 oldVnode = emptyNodeAt(oldVnode);
 }

我們找一個比較簡單的class模塊來看下其源碼:

function updateClass(oldVnode, vnode) {
 var cur, name, elm = vnode.elm, oldClass = oldVnode.data.class, klass = vnode.data.class;
 if (!oldClass && !klass)
 return;
 if (oldClass === klass)
 return;
 oldClass = oldClass || {};
 klass = klass || {};
 for (name in oldClass) {
 if (!klass[name]) {
 elm.classList.remove(name);
 }
 }
 for (name in klass) {
 cur = klass[name];
 if (cur !== oldClass[name]) {
 elm.classList[cur ? 'add' : 'remove'](name);
 }
 }
}
exports.classModule = { create: updateClass, update: updateClass };
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = exports.classModule;

},{}]},{},[1])(1)
});

可以看出create和update鉤子方法調用的時候,可以執行class模塊的updateClass:從elm中刪除vnode中不存在的或者值為false的類。

將vnode中新的class添加到elm上去。

感謝各位的閱讀!關于“vue中Virtual Dom實現snabbdom解密的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

英超| 碌曲县| 铁岭市| 祁阳县| 白水县| 泾川县| 宜春市| 新闻| 烟台市| 玉龙| 弋阳县| 新平| 泽州县| 隆回县| 噶尔县| 长子县| 郑州市| 都江堰市| 泾源县| 克什克腾旗| 蒙山县| 张掖市| 卓尼县| 巧家县| 宜州市| 湘阴县| 定南县| 东宁县| 工布江达县| 民勤县| 连云港市| 曲周县| 东莞市| 富锦市| 社会| 邵东县| 沽源县| 昌乐县| 武夷山市| 安新县| 开化县|