>[success] # patch 方法分析(二) ~~~ 1.上一个章节分析后,patch更新主要分为两块,一块是新老虚拟dom相同,一块是不同,这里是对 不同位置的分析 ~~~ >[info] ## 分析patch 里面createElm ~~~ 注意:这一步只是将虚拟dom 的结构创建为真实dom保存在虚拟dom属性的'elm'中,此时并没有 将虚拟dom构建的dom插入整个页面中 1.这部分做了: 1.1.返回创建的 DOM 元素这也就是代码中的'vnode.elm' 1.2.创建 vnode 对应的 DOM 元素 2.执行顺序: 2.1.首先触发用户设置的 init 钩子函数 2.2.如果选择器是!,创建评论节点 2.3.如果选择器为空,创建文本节点 2.4.如果选择器不为空 2.4.1.解析选择器,设置标签的 id 和 class 属性 2.4.2.执行模块的 create 钩子函数 2.4.3.如果 vnode 有 children,创建子 vnode 对应的 DOM,追加到 DOM 树 2.4.4.如果 vnode 的 text 值是 string/number,创建文本节点并追击到 DOM 树 2.4.5.执行用户设置的 create 钩子函数 2.4.6.如果有用户设置的 insert 钩子函数,把 vnode 添加到队列中 3.源码位置:src/snabbdom.ts ~~~ >[danger] ##### 代码分析 ~~~ts function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node { let i: any, data = vnode.data; if (data !== undefined) { // 执行用户设置的 init 钩子函数 const init = data.hook ? .init; if (isDef(init)) { init(vnode); data = vnode.data; } } let children = vnode.children, sel = vnode.sel; if (sel === '!') { // 如果选择器是!,创建评论节点 if (isUndef(vnode.text)) { vnode.text = ''; } vnode.elm = api.createComment(vnode.text!); } else if (sel !== undefined) { // 如果选择器不为空 // 解析选择器 // Parse selector const 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 elm = vnode.elm = isDef(data) && isDef(i = data.ns) ? api.createElementNS(i, tag) : api.createElement(tag); // 在这之前都是对虚拟dom 的sel属性进行解析,为了对提供的sel 选择器而创建dom准备的 // 注意创建真实dom 有两种一种是createElementNS这是创建svg,一种是createElement 创建普通dom // --------------------------------------------- if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot)); if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' ')); // 执行模块的 create 钩子函数 for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode); // 如果 vnode 中有子节点,创建子 vnode 对应的 DOM 元素并追加到 DOM 树上 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)); } } } else if (is.primitive(vnode.text)) { // 如果 vnode 的 text 值是 string/number,创建文本节点并追加到 DOM 树 api.appendChild(elm, api.createTextNode(vnode.text)); } const hook = vnode.data!.hook; if (isDef(hook)) { // 执行用户传入的钩子 create hook.create ? .(emptyNode, vnode); if (hook.insert) { // 把 vnode 添加到队列中,为后续执行 insert 钩子做准备 insertedVnodeQueue.push(vnode); } } } else { // 如果选择器为空,创建文本节点 vnode.elm = api.createTextNode(vnode.text!); } // 返回新创建的 DOM return vnode.elm; } ~~~ >[info] ## addVnodes 和 removeVnodes 函数 ~~~ function addVnodes(parentElm: Node, before: Node | null, vnodes: Array<VNode>, startIdx: number, endIdx: number, insertedVnodeQueue: VNodeQueue) { for (; startIdx <= endIdx; ++startIdx) { const ch = vnodes[startIdx]; if (ch != null) { api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before); } } } ~~~ ~~~ function removeVnodes(parentElm: Node, vnodes: Array<VNode>, startIdx: number, endIdx: number): void { for (; startIdx <= endIdx; ++startIdx) { let i: any, listeners: number, rm: () => void, ch = vnodes[startIdx]; if (ch != null) { // 如果 sel 有值 if (isDef(ch.sel)) { // 执行 destroy 钩子函数(会执行所有子节点的 destroy 钩子函数) invokeDestroyHook(ch); listeners = cbs.remove.length + 1; // 创建删除的回调函数 rm = createRmCb(ch.elm as Node, listeners); for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm); // 执行用户设置的 remove 钩子函数 if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) { i(ch, rm); } else { // 如果没有用户钩子函数,直接调用删除元素的方法 rm(); } } else { // Text node // 如果是文本节点,直接调用删除元素的方法 api.removeChild(parentElm, ch.elm as Node); } } } } function invokeDestroyHook(vnode: VNode) { let i: any, j: number, data = vnode.data; if (data !== undefined) { // 执行用户设置的 destroy 钩子函数 if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode); // 调用模块的 distroy 钩子函数 for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode); // 执行子节点的 distroy 钩子函数 if (vnode.children !== undefined) { for (j = 0; j < vnode.children.length; ++j) { i = vnode.children[j]; if (i != null && typeof i !== "string") { invokeDestroyHook(i); } } } } } function createRmCb(childElm: Node, listeners: number) { // 返回删除元素的回调函数 return function rmCb() { if (--listeners === 0) { const parent = api.parentNode(childElm); api.removeChild(parent, childElm); } }; } ~~~