亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Vue中虛擬DOM的簡單實(shí)現(xiàn)

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

虛擬DOM

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

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

2. VNode類

省略了部分屬性

/** src/core/vdom/vnode.ts **/
export default class VNode {
? ? tag; ? ? ? ? ? ? ? ? // 當(dāng)前節(jié)點(diǎn)的標(biāo)簽名
? ? data; ? ? ? ? ? ? ? ?// 當(dāng)前節(jié)點(diǎn)對應(yīng)的對象,包含了一些具體數(shù)據(jù)信息
? ? children; ? ? ? ? ? ?// 當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)數(shù)組
? ? text; ? ? ? ? ? ? ? ?// 當(dāng)前節(jié)點(diǎn)的文本
? ? elm; ? ? ? ? ? ? ? ? // 當(dāng)前節(jié)點(diǎn)對應(yīng)的真實(shí)DOM節(jié)點(diǎn)
? ? context; ? ? ? ? ? ? // 當(dāng)前節(jié)點(diǎn)的上下文(Vue實(shí)例)
? ? componentInstance; ? // 當(dāng)前節(jié)點(diǎn)對應(yīng)的組件實(shí)例
? ? parent; ? ? ? ? ? ? ?// 當(dāng)前節(jié)點(diǎn)對應(yīng)的真實(shí)的父DOM節(jié)點(diǎn)
? ? // diff 優(yōu)化的屬性
? ? isStatic; ? ? ? ? ? ?// 是否靜態(tài)節(jié)點(diǎn),是則跳過 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é)點(diǎn)

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

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

3.2 文本節(jié)點(diǎn)

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

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

3.3 克隆節(jié)點(diǎn)

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

克隆時(shí)會新建一個(gè) VNode實(shí)例 ,然后將需要復(fù)制的節(jié)點(diǎn)信息 淺拷貝 到新的節(jié)點(diǎn)上,并通過 vnode.isCloned 標(biāo)識該節(jié)點(diǎn)是克隆節(jié)點(diǎn)。

/** 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é)點(diǎn)

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

3.5 組件節(jié)點(diǎn)

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

  • componentOptions: 組件的 option選項(xiàng),如組件的 props 等
  • componentInstance: 組件節(jié)點(diǎn)對應(yīng)的 Vue實(shí)例

3.6 函數(shù)式組件節(jié)點(diǎn)

函數(shù)式組件節(jié)點(diǎn)相較于組件節(jié)點(diǎn)又有兩個(gè)特有屬性:

  1. fnContext: 函數(shù)式組件對應(yīng)的 Vue實(shí)例
  2. fnOptions: 組件的 option選項(xiàng)

4. 總結(jié)

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

Diff

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

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

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

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

2. 刪除節(jié)點(diǎn)

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

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

3. 更新節(jié)點(diǎn)

更新節(jié)點(diǎn)比較復(fù)雜:

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

4. 總結(jié)

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

更新子節(jié)點(diǎn)

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

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

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

1. 新前與舊前相同

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

2. 新后與舊后相同

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

3. 新后與舊前相同

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

4. 新前與舊后相同

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

5. 不滿足以上4種情況

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

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

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

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

相關(guān)文章

最新評論