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

Vue中虛擬DOM的簡單實現

 更新時間:2023年05月23日 10:09:59   作者:劉狗蛋O_o  
Vue中的虛擬DOM是通過JavaScript對象來描述真實DOM結構的一種方式,本文將介紹Vue中虛擬DOM的簡單實現,具有一定的參考價值,感興趣的可以了解一下

虛擬DOM

1. 什么是虛擬DOM?為什么要有虛擬DOM?

  • 虛擬DOM 是用于描述 DOM 節(jié)點的 JS 對象。
  • 操作真實DOM非常 耗費性能 。盡可能 減少 對 DOM 的操作,通過犧牲 JS 的計算性能來換取操作 DOM 所消耗的性能。

2. VNode類

省略了部分屬性

/** src/core/vdom/vnode.ts **/
export default class VNode {
? ? tag; ? ? ? ? ? ? ? ? // 當前節(jié)點的標簽名
? ? data; ? ? ? ? ? ? ? ?// 當前節(jié)點對應的對象,包含了一些具體數據信息
? ? children; ? ? ? ? ? ?// 當前節(jié)點的子節(jié)點數組
? ? text; ? ? ? ? ? ? ? ?// 當前節(jié)點的文本
? ? elm; ? ? ? ? ? ? ? ? // 當前節(jié)點對應的真實DOM節(jié)點
? ? context; ? ? ? ? ? ? // 當前節(jié)點的上下文(Vue實例)
? ? componentInstance; ? // 當前節(jié)點對應的組件實例
? ? parent; ? ? ? ? ? ? ?// 當前節(jié)點對應的真實的父DOM節(jié)點
? ? // diff 優(yōu)化的屬性
? ? isStatic; ? ? ? ? ? ?// 是否靜態(tài)節(jié)點,是則跳過 diff
? ? constructor(
? ? ? ? tag?,
? ? ? ? data?,
? ? ? ? children?,
? ? ? ? text?,
? ? ? ? elm?,
? ? ? ? context?
? ? ) {
? ? ? ? this.tag = tag;
? ? ? ? this.data = data;
? ? ? ? this.children = children;
? ? ? ? this.text = text;
? ? ? ? this.elm = elm;
? ? ? ? this.context = context;
? ? ? ? this.componentInstance = undefined;
? ? ? ? this.parent = undefined;
? ? ? ? this.isStatic = false;
? ? }
}

3. VNode的類型

3.1 注釋節(jié)點

注釋節(jié)點的描述非常簡單: vnode.text 表示注釋內容, vnode.isComment 表示是一個注釋節(jié)點。

/** src/core/vdom/vnode.ts **/
export const createEmptyVNode = (text) => {
? ? const node = new VNode();
? ? node.text = text;
? ? node.isComment = true;
? ? return node;
};

3.2 文本節(jié)點

文本節(jié)點 的描述比 注釋節(jié)點 更簡單,只需要一個 text 屬性。

/** src/core/vdom/vnode.ts **/
export function createTextVNode(val) {
? ? return VNode(undefined, undefined, undefined, String(val));
};

3.3 克隆節(jié)點

克隆節(jié)點是復制一個已存在的節(jié)點,主要是為了做 模版編譯優(yōu)化 時使用。

克隆時會新建一個 VNode實例 ,然后將需要復制的節(jié)點信息 淺拷貝 到新的節(jié)點上,并通過 vnode.isCloned 標識該節(jié)點是克隆節(jié)點。

/** src/core/vdom/vnode.ts **/
export function cloneVNode(vnode: VNode): VNode {
? const cloned = new VNode(
? ? vnode.tag,
? ? vnode.data,
? ? vnode.children && vnode.children.slice(),
? ? vnode.text,
? ? vnode.elm,
? ? vnode.context,
? ? vnode.componentOptions,
? ? vnode.asyncFactory
? );
? cloned.ns = vnode.ns;
? cloned.isStatic = vnode.isStatic;
? cloned.key = vnode.key;
? cloned.isComment = vnode.isComment;
? cloned.fnContext = vnode.fnContext;
? cloned.fnOptions = vnode.fnOptions;
? cloned.fnScopeId = vnode.fnScopeId;
? cloned.asyncMeta = vnode.asyncMeta;
? cloned.isCloned = true;
? return cloned;
};

3.4 元素節(jié)點

/** src/core/vdom/create-element.ts **/
export function _createElement(
? ? context,
? ? tag?,
? ? data?,
? ? children?
) {
? ? // 如果 data 存在且已轉成可觀測對象,則返回一個注釋節(jié)點
? ? if (isDef(data) && isDef(data.__ob__)) {
? ? ? ? return createEmptyVNode();
? ? }
? ? // 根據 data.is 重新給 tag 賦值
? ? // :is 的實現原理
? ? if (isDef(data) && isDef(data.is)) {
? ? ? ? tag = data.is;
? ? }
? ? // 如果不存在 tag ,則返回一個注釋節(jié)點
? ? if (!tag) {
? ? ? ? return createEmptyVNode();
? ? }
? ? let vnode;
? ? if (typeof tag === 'string') {
? ? ? ? let Ctor;
? ? ? ? if ((!data || !data.pre) && isDef((Ctor = resolveAsset(context.$options, 'components', tag)))) {
? ? ? ? ? ? // 根據 tag 從 options.components 中獲取要創(chuàng)建的組件節(jié)點
? ? ? ? ? ? vnode = createComponent(Ctor, data, context, children);
? ? ? ? } else {
? ? ? ? ? ? // 創(chuàng)建普通的 vnode 節(jié)點
? ? ? ? ? ? vnode = new VNode(tag, data, children, undefined, undefined, context);
? ? ? ? }
? ? } else {
? ? ? ? // 創(chuàng)建組件節(jié)點
? ? ? ? vnode = createComponent(tag, data, context, children);
? ? }
? ? return vnode;
}

3.5 組件節(jié)點

組件節(jié)點除了元素節(jié)點具有的屬性外,還有兩個特有屬性:

  • componentOptions: 組件的 option選項,如組件的 props 等
  • componentInstance: 組件節(jié)點對應的 Vue實例

3.6 函數式組件節(jié)點

函數式組件節(jié)點相較于組件節(jié)點又有兩個特有屬性:

  1. fnContext: 函數式組件對應的 Vue實例
  2. fnOptions: 組件的 option選項

4. 總結

在視圖渲染之前,將寫好的 template模版 編譯成 vnode 緩存下來。等到 數據發(fā)生變化 頁面需要 重新渲染 時,將數據發(fā)生變化后生成的 vnode 與前一次緩存的 vnode 進行對比,找出差異,根據 有差異的vnode 創(chuàng)建 真實DOM節(jié)點再插入到視圖中,完成試圖更新。

Diff

1. 創(chuàng)建節(jié)點

為了避免直接修改 vnode 而引起 狀態(tài)混亂 問題,創(chuàng)建節(jié)點時若 vnode 已被之前的渲染使用,則 克隆該節(jié)點 ,修改克隆的 vnode 的屬性。

創(chuàng)建節(jié)點時,會根據當前 宿主環(huán)境 調用封裝好的 nodeOps.createElement() 方法,在 web端 等同于 document.createElement() 。

/** src/core/vdom/patch.ts **/
function createElm(
? ? vnode,
? ? insertedVnodeQueue,
? ? parentElm,
? ? refElm,
? ? nested,
? ? ownerArray,
? ? index
) {
? ? // vnode 有真實DOM節(jié)點時,克隆生成新的 vnode
? ? if (isDef(vnode.elm) && isDef(ownerArray)) {
? ? ? ? vnode = ownerArray[index] = cloneVNode(vnode);
? ? }
? ? // 是否是組件的根節(jié)點
? ? vnode.isRootInsert = !nested;
? ? const data = vnode.data;
? ? const children = vnode.children;
? ? const tag = vnode.tag;
? ? if (isDef(tag)) {
? ? ? ? // tag 不為 null/undefined 時
? ? ? ? // 創(chuàng)建新的真實DOM節(jié)點
? ? ? ? vnode.elm = nodeOps.createElement(tag, vnode);
? ? ? ? // 創(chuàng)建子節(jié)點
? ? ? ? createChildren(vnode, children, insertedVnodeQueue);
? ? ? ? // 插入節(jié)點
? ? ? ? insert(parentElm, vnode.elm, refElm);
? ? } else if (isTrue(vnode.isComment)) {
? ? ? ? // 創(chuàng)建注釋節(jié)點
? ? ? ? vnode.elm = nodeOps.createComment(vnode.text);
? ? ? ? insert(parentElm, vnode.elm, refElm);
? ? } else {
? ? ? ? // 創(chuàng)建文本節(jié)點
? ? ? ? vnode.elm = nodeOps.createTextNode(vnode.text);
? ? ? ? insert(parentElm, vnode.elm, refElm);
? ? }
}

2. 刪除節(jié)點

刪除節(jié)點的邏輯非常簡單,找到 父節(jié)點 再移除 子節(jié)點 。

/** src/core/vdom/patch.ts **/
function removeNode(el) {
? ? const parent = nodeOps.parentNode(el);
? ? nodeOps.removeChild(parent, el);
}

3. 更新節(jié)點

更新節(jié)點比較復雜:

  • 判斷新/舊節(jié)點是否相同,若 新/舊節(jié)點相同 , 則 結束更新流程
  • 克隆節(jié)點
  • 新舊節(jié)點是否為 靜態(tài)節(jié)點 ,新/舊節(jié)點的 key 是否相同,新節(jié)點是否為 克隆節(jié)點 或新節(jié)點是否只創(chuàng)建一次。若 新/舊節(jié)點都為靜態(tài)節(jié)點,新舊節(jié)點的 key相同 ,新節(jié)點為克隆節(jié)點或新節(jié)點只能被創(chuàng)建一次 ,則更新 vnode.componentInstance 并 結束更新流程
  • 新節(jié)點是否包含文本
  • 新節(jié)點不包含文本
    • 新/舊節(jié)點都包含子節(jié)點,且子節(jié)點不同 ,則 更新子節(jié)點
    • 只有新節(jié)點包含子節(jié)點 ,則 清空DOM中的文本內容,并更新子節(jié)點
    • 只有舊節(jié)點包含子節(jié)點 ,則 移除子節(jié)點
    • 新/舊節(jié)點都不包含子節(jié)點,且舊節(jié)點包含文本 ,則 清空DOM中的文本內容
  • 新節(jié)點包含文本,且新/舊節(jié)點的文本不同 ,則 更新DOM中的文本內容
/** src/core/vdom/patch.ts **/
function patchVnode(
? ? oldVnode,
? ? vnode,
? ? insertedVnodeQueue,
? ? ownerArray,
? ? index,
? ? removeOnly?
) {
? ? // 新、舊節(jié)點相同,直接返回
? ? if (oldVnode === vnode) {
? ? ? ? return;
? ? }
? ? // 克隆節(jié)點,為什么需要克隆節(jié)點的原因不做贅述
? ? if (isDef(vnode.elm) && isDef(ownerArray)) {
? ? ? ? vnode = ownerArray[index] = cloneVNode(vnode);
? ? }
? ? // 重新對克隆節(jié)點的真實DOM賦值
? ? const elm = (vnode.elm = oldVnode.elm);
? ? // vnode 與 oldVnode 都是靜態(tài)節(jié)點,且 key 相同,直接返回
? ? if (
? ? ? ? isTrue(vnode.isStatic) &&
? ? ? ? isTrue(oldVnode.isStatic) &&
? ? ? ? vnode.key === oldVnode.key &&
? ? ? ? (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
? ? ) {
? ? ? ? vnode.componentInstance = oldVnode.componentInstance;
? ? ? ? return;
? ? }
? ? const oldCh = oldVnode.children;
? ? const ch = vnode.children;
? ? // vnode 沒有文本屬性
? ? if (isUndef(vnode.text)) {
? ? ? ? if (isDef(oldCh) && isDef(ch)) {
? ? ? ? ? ? // 若存在 oldCh 和 ch ,且二者不同
? ? ? ? ? ? // 則更新子節(jié)點
? ? ? ? ? ? if (oldCh !== ch) {
? ? ? ? ? ? ? ? updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
? ? ? ? ? ? }
? ? ? ? } else if (isDef(ch)) {
? ? ? ? ? ? // 若只存在 ch?
? ? ? ? ? ? // 則清空 DOM 中的文本,再添加子節(jié)點
? ? ? ? ? ? if (isDef(oldVnode.text)) {
? ? ? ? ? ? ? ? nodeOps.setTextContent(elm, '');
? ? ? ? ? ? }
? ? ? ? ? ? addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
? ? ? ? } else if (isDef(oldCh)) {
? ? ? ? ? ? // 若只存在 oldCh
? ? ? ? ? ? // 則移除子節(jié)點
? ? ? ? ? ? removeVnodes(oldCh, 0, oldCh.length - 1);
? ? ? ? } else if (isDef(oldVnode.text)) {
? ? ? ? ? ? // 若都沒有子節(jié)點,且 oldVnode 有文本屬性
? ? ? ? ? ? // 則清空 DOM 中的文本
? ? ? ? ? ? nodeOps.setTextContent(elm, '');
? ? ? ? }
? ? } else if (oldVnode.text !== vnode.text) {
? ? ? ? // vnode 有文本屬性則與 oldVnode 的文本屬性比較
? ? ? ? // 若有差異則更新為新的文本
? ? ? ? nodeOps.setTextContent(elm, vnode.text);
? ? }
}

4. 總結

Vue 的 diff算法(patch) 的過程干了三件事: 創(chuàng)建節(jié)點,刪除節(jié)點,更新節(jié)點。

更新子節(jié)點

Vue 中通過 updateChildren()方法 更新子節(jié)點。其思想就是循環(huán)新/舊子節(jié)點,然后對比。這部分代碼較長,按照代碼結構逐步分析。

對源碼中的一些屬性的中文名作以下約定:

  • 新子節(jié)點數組中第一個未處理的子節(jié)點: newStartVnode 新前
  • 新子節(jié)點數組中最后一個未處理的子節(jié)點:newEndVnode 新后
  • 舊子節(jié)點數組中第一個未處理的子節(jié)點: oldStartVnode 舊前
  • 舊子節(jié)點數組中最后一個未處理的子節(jié)點:oldEndVnode 舊后

1. 新前與舊前相同

/** src/core/vdom/patch.ts **/
else if (sameVnode(oldStartVnode, newStartVnode)) {
? ?// 進入 patch 流程,對比新舊 vnode 更新節(jié)點
? ?patchVnode(
? ? ? ?oldStartVnode,
? ? ? ?newStartVnode,
? ? ? ?insertedVnodeQueue,
? ? ? ?newCh,
? ? ? ?newStartIdx
? ?);
? ?// 從前向后切換待處理子節(jié)點
? ?oldStartVnode = oldCh[++oldStartIdx];
? ?newStartVnode = newCh[++newStartIdx];
}

2. 新后與舊后相同

/** src/core/vdom/patch.ts **/
else if (sameVnode(oldEndVnode, newEndVnode)) {
? ?// 進入 patch 流程,對比新舊 vnode 更新節(jié)點
? ?patchVnode(
? ? ? ?oldEndVnode,
? ? ? ?newEndVnode,
? ? ? ?insertedVnodeQueue,
? ? ? ?newCh,
? ? ? ?newEndIdx
? ?);
? ?// 從后向前切換待處理子節(jié)點
? ?oldEndVnode = oldCh[--oldEndIdx];
? ?newEndVnode = newCh[--newEndIdx];
}

3. 新后與舊前相同

/** src/core/vdom/patch.ts **/
else if (sameVnode(oldStartVnode, newEndVnode)) {
? ? // 進入 patch 流程,更新節(jié)點
? ? // 省略調用 patchVnode 的代碼
? ? // ...
? ? // 將舊前節(jié)點插到舊后節(jié)點后面
? ? nodeOps.insertBefore(
? ? ? ? parentElm,
? ? ? ? oldStartVnode.elm,
? ? ? ? nodeOps.nextSibling(oldEndVnode.elm)
? ? );
? ? // 切換待處理子節(jié)點
? ? oldStartVnode = oldCh[++oldStartIdx];
? ? newEndVnode = newCh[--newEndIdx];
}

4. 新前與舊后相同

/** src/core/vdom/patch.ts **/
else if (sameVnode(oldEndVnode, newStartVnode)) {
? ? // 進入 patch 流程,更新節(jié)點
? ? // 省略調用 patchVnode 的代碼
? ? // ...
? ? // 將舊后節(jié)點插到舊前前面
? ? nodeOps.insertBefore(
? ? ? ? parentElm,
? ? ? ? oldEndVnode.elm,
? ? ? ? oldStartVnode.elm
? ? );
? ? // 切換待處理子節(jié)點
? ? oldEndVnode = oldCh[--oldEndIdx];
? ? newStartVnode = newCh[++newStartIdx];
}

5. 不滿足以上4種情況

/** src/core/vdom/patch.ts **/
else {
? ? // idxInOld 有兩種取值方法
? ? // 1. 獲取 舊節(jié)點數組中 與 新節(jié)點的 key 相同的 vnode 的索引
? ? // 2. 舊節(jié)點數組中 與 新節(jié)點相同的 vnode 的索引
? ? if (isUndef(idxInOld)) {
? ? ? ? // 舊子節(jié)點數組 中不存在 新前
? ? ? ? // 創(chuàng)建新元素
? ? ? ? createElm(
? ? ? ? ? ? newStartVnode,
? ? ? ? ? ? insertedVnodeQueue,
? ? ? ? ? ? parentElm,
? ? ? ? ? ? oldStartVnode.elm,
? ? ? ? ? ? false,
? ? ? ? ? ? newCh,
? ? ? ? ? ? newStartIdx
? ? ? ? );
? ? } else {
? ? ? ? vnodeToMove = oldCh[idxInOld];
? ? ? ? if (sameVnode(vnodeToMove, newStartVnode)) {
? ? ? ? ? ? // 進入 patch 流程,更新節(jié)點
? ? ? ? ? ? // 省略調用 patchVnode 的代碼
? ? ? ? ? ? // ...
? ? ? ? ? ? oldCh[idxInOld] = undefined;
? ? ? ? ? ? // 將節(jié)點移動到 舊前節(jié)點 前面
? ? ? ? ? ? nodeOps.insertBefore(
? ? ? ? ? ? ? ? parentElm,
? ? ? ? ? ? ? ? vnodeToMove.elm,
? ? ? ? ? ? ? ? oldStartVnode.elm
? ? ? ? ? ? );
? ? ? ? } else {
? ? ? ? ? ? // vnode 不同,則創(chuàng)建新元素
? ? ? ? ? ? createElm(
? ? ? ? ? ? ? ? newStartVnode,
? ? ? ? ? ? ? ? insertedVnodeQueue,
? ? ? ? ? ? ? ? parentElm,
? ? ? ? ? ? ? ? oldStartVnode.elm,
? ? ? ? ? ? ? ? false,
? ? ? ? ? ? ? ? newCh,
? ? ? ? ? ? ? ? newStartIdx
? ? ? ? ? ? );
? ? ? ? }
? ? }
}

6. 結束while循環(huán)中的邏輯后

if (oldStartIdx > oldEndIdx) {
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
    // 插入新節(jié)點
    addVnodes(
        parentElm,
        refElm,
        newCh,
        newStartIdx,
        newEndIdx,
        insertedVnodeQueue
    );
} else if (newStartIdx > newEndIdx) {
    // 移除舊子節(jié)點數組中剩余未處理的節(jié)點
    removeNodes(oldCh, oldStartIdx, oldEndIdx);
}

到此這篇關于Vue中虛擬DOM的簡單實現的文章就介紹到這了,更多相關Vue 虛擬DOM內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論

呼伦贝尔市| 治多县| 东山县| 崇义县| 镇沅| 闻喜县| 阳山县| 泗水县| 明水县| 石狮市| 广州市| 淮安市| 金门县| 普洱| 上饶县| 公安县| 射洪县| 白朗县| 普定县| 宝丰县| 门源| 乐安县| 富蕴县| 荔波县| 永城市| 普陀区| 富宁县| 玛纳斯县| 疏勒县| 比如县| 山东| 兴文县| 报价| 苍南县| 松阳县| 泰宁县| 黎川县| 淮安市| 台东市| 迁安市| 边坝县|