网站建设运行环境/如何销售自己产品方法有哪些
在Vue开发过程中vfor遍历数组创建Dom是最常见的方式,在vfor时,标签中有一个key值,key值的作用是啥呢?这就不得不提到Vue中的diff算法。
一、什么是diff算法
Vue会用虚拟DOM来表述真实DOM,这样的目的是为了计算出DOM的最小的变化从而更加快速的更新真实DOM
二、diff算法的计算过程
1、遍历老虚拟DOM
2、遍历新虚拟DOM
3、重新排序
这样做会有个问题,就是节点数越多,计算的次数以指数级增长,时间复杂度为O(n³),这个计算次数是不可接受的,所以在Vue中对diff算法做了优化:
1、只比较同一层级,不做跨级比较
2、标签名不同直接删除,不继续深度比较
3、标签名相同,key相同,就被认为是相同节点,不继续深度比较
所以在vfor创建变量时,不建议使用item.index作为key值,而是以item.id作为key值来保证性能
在优化完成后,算法的时间复杂度为O(n)
三、patch函数
vue中的patch函数的逻辑是基于shabbdom,patch函数会在页面首次渲染时执行一次,这一步的目的是将Vnode渲染到一个空的容器中
const vnode = h("div#container.two.classes",{ on: { click: () => console.log("div clicked") } },[h("span", { style: { fontWeight: "bold" } }, "This is bold")," and this is just normal text",h("a", { props: { href: "/foo" } }, "I'll take you places!")]
);
// Patch into empty DOM element – this modifies the DOM as a side effect
patch(container, vnode);
在页面更新时用新的Vnode来替换老的Vnode。
const newVnode = h("div#container.two.classes",{ on: { click: () => console.log("updated div clicked") } },[h("span",{ style: { fontWeight: "normal", fontStyle: "italic" } },"This is now italic type")," and this is still just normal text",h("a", { props: { href: "/bar" } }, "I'll take you places!")]
);
// Second `patch` invocation
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
patch函数是通过init函数创建出来的
const patch = init([// Init patch function with chosen modulesclassModule, // makes it easy to toggle classespropsModule, // for setting properties on DOM elementsstyleModule, // handles styling on elements with support for animationseventListenersModule // attaches event listeners
]);
这里贴上patch实现的逻辑,路径:src>package>init.ts
return function patch(oldVnode: VNode | Element | DocumentFragment,vnode: VNode): VNode {let i: number, elm: Node, parent: Node;const insertedVnodeQueue: VNodeQueue = [];for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();if (isElement(api, oldVnode)) {oldVnode = emptyNodeAt(oldVnode);} else if (isDocumentFragment(api, oldVnode)) {oldVnode = emptyDocumentFragmentAt(oldVnode);}if (sameVnode(oldVnode, vnode)) {patchVnode(oldVnode, vnode, insertedVnodeQueue);} else {elm = oldVnode.elm!;parent = api.parentNode(elm) as Node;//创建新的DOM元素createElm(vnode, insertedVnodeQueue);if (parent !== null) {//插入新的DOM元素api.insertBefore(parent, vnode.elm!, api.nextSibling(elm));//移除老的DOM元素removeVnodes(parent, [oldVnode], 0, 0);}}for (i = 0; i < insertedVnodeQueue.length; ++i) {insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i]);}for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();return vnode;};
isElement方法用于判断我们传入patch的值是不是一个Dom元素,返回一个布尔值
function isElement(api: DOMAPI,vnode: Element | DocumentFragment | VNode
): vnode is Element {return api.isElement(vnode as any);
}
sameVnode方法判断oldVnode与现有的Vnode是不是同一个Vnode,返回一个布尔值
function sameVnode(vnode1: VNode, vnode2: VNode): boolean {const isSameKey = vnode1.key === vnode2.key;const isSameIs = vnode1.data?.is === vnode2.data?.is;const isSameSel = vnode1.sel === vnode2.sel;const isSameTextOrFragment =!vnode1.sel && vnode1.sel === vnode2.sel? typeof vnode1.text === typeof vnode2.text: true;return isSameSel && isSameKey && isSameIs && isSameTextOrFragment;
}
patchVnode用于更新Vnode
function patchVnode(oldVnode: VNode,vnode: VNode,insertedVnodeQueue: VNodeQueue) {const hook = vnode.data?.hook;hook?.prepatch?.(oldVnode, vnode);const elm = (vnode.elm = oldVnode.elm)!;if (oldVnode === vnode) return;if (vnode.data !== undefined ||(vnode.text !== undefined && vnode.text !== oldVnode.text)) {vnode.data ??= {};oldVnode.data ??= {};for (let i = 0; i < cbs.update.length; ++i)cbs.update[i](oldVnode, vnode);vnode.data?.hook?.update?.(oldVnode, vnode);}const oldCh = oldVnode.children as VNode[];const ch = vnode.children as VNode[];if (vnode.text === undefined) {if (oldCh !== undefined && ch !== undefined) {if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);} else if (ch !== undefined) {if (oldVnode.text !== undefined) api.setTextContent(elm, "");addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);} else if (oldCh !== undefined) {removeVnodes(elm, oldCh, 0, oldCh.length - 1);} else if (oldVnode.text !== undefined) {api.setTextContent(elm, "");}} else if (oldVnode.text !== vnode.text) {if (oldCh !== undefined) {removeVnodes(elm, oldCh, 0, oldCh.length - 1);}api.setTextContent(elm, vnode.text);}hook?.postpatch?.(oldVnode, vnode);}
createElm用于创建新的DOM元素
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {let i: number;const data = vnode.data;const hook = data?.hook;hook?.init?.(vnode);const children = vnode.children;const sel = vnode.sel;if (sel === "!") {vnode.text ??= "";vnode.elm = api.createComment(vnode.text);} else if (sel === "") {// textNode has no selectorvnode.elm = api.createTextNode(vnode.text!);} else if (sel !== undefined) {// Parse selectorconst hashIdx = sel.indexOf("#");const dotIdx = sel.indexOf(".", hashIdx);const hash = hashIdx > 0 ? hashIdx : sel.length;const dot = dotIdx > 0 ? dotIdx : sel.length;const tag =hashIdx !== -1 || dotIdx !== -1? sel.slice(0, Math.min(hash, dot)): sel;const ns = data?.ns;const elm =ns === undefined? api.createElement(tag, data): api.createElementNS(ns, tag, data);vnode.elm = elm;if (hash < dot) elm.setAttribute("id", sel.slice(hash + 1, dot));if (dotIdx > 0)elm.setAttribute("class", sel.slice(dot + 1).replace(/\./g, " "));for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);if (is.primitive(vnode.text) &&(!is.array(children) || children.length === 0)) {// allow h1 and similar nodes to be created w/ text and empty child listapi.appendChild(elm, api.createTextNode(vnode.text));}if (is.array(children)) {for (i = 0; i < children.length; ++i) {const ch = children[i];if (ch != null) {api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));}}}if (hook !== undefined) {hook.create?.(emptyNode, vnode);if (hook.insert !== undefined) {insertedVnodeQueue.push(vnode);}}} else if (options?.experimental?.fragments && vnode.children) {vnode.elm = (api.createDocumentFragment ?? documentFragmentIsNotSupported)();for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);for (i = 0; i < vnode.children.length; ++i) {const ch = vnode.children[i];if (ch != null) {api.appendChild(vnode.elm,createElm(ch as VNode, insertedVnodeQueue));}}} else {vnode.elm = api.createTextNode(vnode.text!);}return vnode.elm;}
removeVnodes方法用于移除老的DOM元素
function removeVnodes(parentElm: Node,vnodes: VNode[],startIdx: number,endIdx: number): void {for (; startIdx <= endIdx; ++startIdx) {let listeners: number;const ch = vnodes[startIdx];if (ch != null) {if (ch.sel !== undefined) {invokeDestroyHook(ch);listeners = cbs.remove.length + 1;const rm = createRmCb(ch.elm!, listeners);for (let i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);const removeHook = ch?.data?.hook?.remove;if (removeHook !== undefined) {removeHook(ch, rm);} else {rm();}} else if (ch.children) {// Fragment nodeinvokeDestroyHook(ch);removeVnodes(parentElm,ch.children as VNode[],0,ch.children.length - 1);} else {// Text nodeapi.removeChild(parentElm, ch.elm!);}}}}
这就是整个patch函数的逻辑