ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] ***** ## 5 [\core\vdom\] 虚拟DOM目录 ### 5-1 目录文件 ~~~ \core\vdom\ vnode.js ;虚拟节点 helper.js patch.js create-elment.js create-component.js modules\index.js modules\directive.js ~~~ ### 5-2 vnode.js(虚拟节点) >[info] module ~~~ ;创建虚拟节点对象 ;tag 标签名称 ;data ;children ;text ;elm ;ns ;context ;key export default function VNode (tag, data, children, text, elm, ns, context) { return { tag, data, children, text, elm, ns, context, key: data && data.key } } ~~~ >[info] export ~~~ export default function VNode (tag, data, children, text, elm, ns, context) {} ~~~ ### 5-3 helper.js(辅助文件) >[info] import ~~~ ;(导入)基础工具,虚拟节点 import { isArray, isPrimitive } from '../util/index' import VNode from './vnode' ~~~ >[info] module ~~~ ;空白节点 const whitespace = VNode(undefined, undefined, undefined, ' ') ;节点扁平化处理 export function flatten (children) { if (typeof children === 'string') { ;子节点为字符串 return [VNode(undefined, undefined, undefined, children)] } if (isArray(children)) { ;子节点为数组 const res = [] for (let i = 0, l = children.length; i < l; i++) { const c = children[i] if (isArray(c)) { ;递归扁平化处理 res.push.apply(res, flatten(c)) } else if (isPrimitive(c)) { if (c === ' ') { ;空白, res.push(whitespace) } else { ;数字,字符串 res.push(VNode(undefined, undefined, undefined, c)) } } else if (c) { ;其他复杂对象 res.push(c) } } ;返回扁平化结果 return res } } ;事件监听器更新 export function updateListeners (on, oldOn, add) { let name, cur, old, event, capture ;遍历新的事件监听器数组on for (name in on) { cur = on[name] old = oldOn[name] ;新增事件监听器 if (old === undefined) { capture = name.charAt(0) === '!' event = capture ? name.slice(1) : name if (isArray(cur)) { ;新增事件监听器为数组 add(event, arrInvoker(cur), capture) } else { ;新增单个事件监听器 cur = { fn: cur } on[name] = cur add(event, fnInvoker(cur), capture) } } else if (isArray(old)) { ;原有事件监听器为数组 old.length = cur.length for (let i = 0; i < old.length; i++) old[i] = cur[i] on[name] = old } else { ;原有事件监听器为单个 old.fn = cur on[name] = old } } } ;数组arr函数回调 function arrInvoker (arr) { return function (ev) { const single = arguments.length === 1 for (let i = 0; i < arr.length; i++) { single ? arr[i](ev) : arr[i].apply(null, arguments) } } } ;单个o函数回调 function fnInvoker (o) { return function (ev) { const single = arguments.length === 1 single ? o.fn(ev) : o.fn.apply(null, arguments) } } ~~~ >[info] export ~~~ ;节点扁平化处理 export function flatten (children) {} ~~~ ### 5-4 patch.js() >[info] import ~~~ ;(导入)虚拟节点,基础工具 import VNode from './vnode' import { isPrimitive, renderString, warn } from '../util/index' ~~~ >[info] module ~~~ ;空节点 const emptyNode = VNode('', {}, []) ;钩子数组 const hooks = ['create', 'update', 'remove', 'destroy'] ;undef检测 function isUndef (s) { return s === undefined } ;def检测 function isDef (s) { return s !== undefined } ;比较两个节点是否相同 function sameVnode (vnode1, vnode2) { return vnode1.key === vnode2.key && vnode1.tag === vnode2.tag } ;索引修正 function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i } return map } ;创建patch函数 export function createPatchFunction (backend) { let i, j const cbs = {} const { modules, nodeOps } = backend //hooks注册 for (i = 0; i < hooks.length; ++i) { cbs[hooks[i]] = [] for (j = 0; j < modules.length; ++j) { if (modules[j][hooks[i]] !== undefined) cbs[hooks[i]].push(modules[j][hooks[i]]) } } //elm元素的空节点 function emptyNodeAt (elm) { return VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm) } ; function createRmCb (childElm, listeners) { function remove () { if (--remove.listeners === 0) { removeElement(childElm) } } remove.listeners = listeners return remove } function removeElement (el) { const parent = nodeOps.parentNode(el) nodeOps.removeChild(parent, el) } ; function createElm (vnode, insertedVnodeQueue) { let i, elm const data = vnode.data if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode) // after calling the init hook, if the vnode is a child component // it should've created a child instance and mounted it. the child // component also has set the placeholder vnode's elm. // in that case we can just return the element and be done. if (isDef(i = vnode.child)) { invokeCreateHooks(vnode, insertedVnodeQueue) return vnode.elm } } const children = vnode.children const tag = vnode.tag if (isDef(tag)) { elm = vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag) if (Array.isArray(children)) { for (i = 0; i < children.length; ++i) { nodeOps.appendChild(elm, createElm(children[i], insertedVnodeQueue)) } } else if (isPrimitive(vnode.text)) { nodeOps.appendChild(elm, nodeOps.createTextNode(vnode.text)) } if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } } else { elm = vnode.elm = nodeOps.createTextNode(vnode.text) } return vnode.elm } function invokeCreateHooks (vnode, insertedVnodeQueue) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, vnode) } i = vnode.data.hook // Reuse variable if (isDef(i)) { if (i.create) i.create(emptyNode, vnode) if (i.insert) insertedVnodeQueue.push(vnode) } } function addVnodes (parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) { for (; startIdx <= endIdx; ++startIdx) { nodeOps.insertBefore(parentElm, createElm(vnodes[startIdx], insertedVnodeQueue), before) } } function invokeDestroyHook (vnode) { let i, j const data = vnode.data if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode) for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode) if (isDef(i = vnode.children)) { for (j = 0; j < vnode.children.length; ++j) { invokeDestroyHook(vnode.children[j]) } } if (isDef(i = vnode.child)) { invokeDestroyHook(i._vnode) } } } function removeVnodes (parentElm, vnodes, startIdx, endIdx) { for (; startIdx <= endIdx; ++startIdx) { const ch = vnodes[startIdx] if (isDef(ch)) { if (isDef(ch.tag)) { invokeDestroyHook(ch) removeAndInvokeRemoveHook(ch) } else { // Text node nodeOps.removeChild(parentElm, ch.elm) } } } } function removeAndInvokeRemoveHook (vnode, rm) { if (rm || isDef(vnode.data)) { const listeners = cbs.remove.length + 1 if (!rm) { // directly removing rm = createRmCb(vnode.elm, listeners) } else { // we have a recursively passed down rm callback // increase the listeners count rm.listeners += listeners } // recursively invoke hooks on child component root node if (isDef(i = vnode.child) && isDef(i = i._vnode) && isDef(i.data)) { removeAndInvokeRemoveHook(i, rm) } for (i = 0; i < cbs.remove.length; ++i) { cbs.remove[i](vnode, rm) } if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) { i(vnode, rm) } else { rm() } } else { removeElement(vnode.elm) } } function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue) { 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, elmToMove, before 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)) { 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)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = oldKeyToIdx[newStartVnode.key] if (isUndef(idxInOld)) { // New element nodeOps.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm) newStartVnode = newCh[++newStartIdx] } else { elmToMove = oldCh[idxInOld] if (process.env.NODE_ENV !== 'production' && !elmToMove) { warn( 'Duplicate track-by key: ' + idxInOld + '. ' + 'Make sure each v-for item has a unique track-by key.' ) } patchVnode(elmToMove, newStartVnode, insertedVnodeQueue) oldCh[idxInOld] = undefined nodeOps.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm) newStartVnode = newCh[++newStartIdx] } } } if (oldStartIdx > oldEndIdx) { before = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) } } function patchVnode (oldVnode, vnode, insertedVnodeQueue) { let i, hook if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) { i(oldVnode, vnode) } // skip nodes with v-pre if (isDef(i = vnode.data) && i.pre) { return } let elm = vnode.elm = oldVnode.elm const oldCh = oldVnode.children const ch = vnode.children if (oldVnode === vnode) return if (!sameVnode(oldVnode, vnode)) { const parentElm = nodeOps.parentNode(oldVnode.elm) elm = createElm(vnode, insertedVnodeQueue) nodeOps.insertBefore(parentElm, elm, oldVnode.elm) removeVnodes(parentElm, [oldVnode], 0, 0) return } if (isDef(vnode.data)) { for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) i = vnode.data.hook if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode) } if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) } else if (isDef(ch)) { if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text) } if (isDef(hook) && isDef(i = hook.postpatch)) { i(oldVnode, vnode) } } function invokeInsertHook (queue) { for (let i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]) } } function hydrate (elm, vnode, insertedVnodeQueue) { if (process.env.NODE_ENV !== 'production') { if (!assertNodeMatch(elm, vnode)) { return false } } vnode.elm = elm const { tag, data, children } = vnode if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode) if (isDef(i = vnode.child)) { // child component. it should have hydrated its own tree. invokeCreateHooks(vnode, insertedVnodeQueue) return true } } if (isDef(tag)) { if (isDef(children)) { const childNodes = elm.childNodes for (let i = 0; i < children.length; i++) { const success = hydrate(childNodes[i], children[i], insertedVnodeQueue) if (!success) { return false } } } if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } } return true } function assertNodeMatch (node, vnode) { if (vnode.tag) { if (vnode.tag.indexOf('vue-component') === 0) { return true } else { return vnode.tag === node.tagName.toLowerCase() && ( vnode.children ? vnode.children.length === node.childNodes.length : node.childNodes.length === 0 ) } } else { return renderString(vnode.text) === node.data } } return function patch (oldVnode, vnode) { let elm, parent const insertedVnodeQueue = [] if (!oldVnode) { // empty mount, create new root element createElm(vnode, insertedVnodeQueue) } else { if (sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue) } else { if (isUndef(oldVnode.tag)) { // mounting to a real element // check if this is server-rendered content and if we can perform // a successful hydration. if (oldVnode.hasAttribute('server-rendered')) { oldVnode.removeAttribute('server-rendered') if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(insertedVnodeQueue) return oldVnode } else if (process.env.NODE_ENV !== 'production') { warn( 'The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. Bailing hydration and performing ' + 'full client-side render.' ) } } // either not server-rendered, or hydration failed. // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode) } elm = oldVnode.elm parent = nodeOps.parentNode(elm) createElm(vnode, insertedVnodeQueue) if (parent !== null) { nodeOps.insertBefore(parent, vnode.elm, nodeOps.nextSibling(elm)) removeVnodes(parent, [oldVnode], 0, 0) } } } invokeInsertHook(insertedVnodeQueue) return vnode.elm } } ~~~ >[info] export ~~~ ;(导出)创建渲染函数 export function createPatchFunction (backend) {} ~~~ ### 5-5 create-element.js(虚拟元素) ### 5-6 create-component.js(虚拟组件) ### 5-7 modules\index.js(入口文件) ### 5-8 modules\directive.js(指令) ![](https://box.kancloud.cn/2016-05-06_572bf6c0146bf.png)