博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.12-Vue源码之patch(2)
阅读量:5052 次
发布时间:2019-06-12

本文共 11450 字,大约阅读时间需要 38 分钟。

快完事咯!  

 

  简单看了下patch函数,虽然不长,但是实际上很长很长,慢慢来吧,

  首先来个总览:

// line-5250    // oldVnode => 原生DOM节点    // vnode => 虚拟DOM    // hydrating => undefined    // removeOnly => false    // 后面两个undefined    function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {        // 判断是否存在虚拟DOM        if (isUndef(vnode)) {            if (isDef(oldVnode)) {                invokeDestroyHook(oldVnode);            }            return        }        var isInitialPatch = false;        var insertedVnodeQueue = [];        if (isUndef(oldVnode)) {            // empty mount (likely as component), create new root element            // 空挂载或作为组件            isInitialPatch = true;            createElm(vnode, insertedVnodeQueue, parentElm, refElm);        } else {            var isRealElement = isDef(oldVnode.nodeType);            if (!isRealElement && sameVnode(oldVnode, vnode)) {                // patch existing root node                patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);            } else {                if (isRealElement) {                    // 判断是否SSR                    if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {                        oldVnode.removeAttribute(SSR_ATTR);                        hydrating = true;                    }                    // hydrating这个到底是啥????                    if (isTrue(hydrating)) {                        if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {                            invokeInsertHook(vnode, insertedVnodeQueue, true);                            return oldVnode                        } else {                            warn(                                'The client-side rendered virtual DOM tree is not matching ' +                                'server-rendered content. This is likely caused by incorrect ' +                                'HTML markup, for example nesting block-level elements inside ' +                                '

, or missing . Bailing hydration and performing ' + 'full client-side render.' ); } } // 两个都不是 新建空虚拟DOM oldVnode = emptyNodeAt(oldVnode); } // 此处oldElm => div | parentElm$1 => body var oldElm = oldVnode.elm; var parentElm$1 = nodeOps.parentNode(oldElm); createElm( vnode, insertedVnodeQueue, // BUG:当节点处于leaving transition状态中不插入 oldElm._leaveCb ? null : parentElm$1, // 获取相邻节点 nodeOps.nextSibling(oldElm) ); if (isDef(vnode.parent)) { // component root element replaced. // update parent placeholder node element, recursively var ancestor = vnode.parent; while (ancestor) { ancestor.elm = vnode.elm; ancestor = ancestor.parent; } if (isPatchable(vnode)) { for (var i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, vnode.parent); } } } if (isDef(parentElm$1)) { removeVnodes(parentElm$1, [oldVnode], 0, 0); } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode); } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm }

  函数总共有6个形参,其中有3个为undefined,内部总共主要是条件判断,按照顺序跑下来。

 

一、

  由于不是SSR,也没有hydrating这个参数,所以会直接创建一个空的虚拟DOM作为oldVnode,如下:

// line-4778    function emptyNodeAt(elm) {        return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)    }

  

二、

  接下来到了createElm方法,代码如下:

// line-4802    // vnode => 之前的虚拟DOM    // insertedVnodeQueue => [] => 空数组    // parentElm => body => 父元素    // refElm => 空白文本 => 节点的相邻元素    // nested => undefined    function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {        vnode.isRootInsert = !nested; // for transition enter check        if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {            return        }        var data = vnode.data;        var children = vnode.children;        var tag = vnode.tag;        if (isDef(tag)) {            // error检测            {                if (data && data.pre) {                    inPre++;                }                // 这个错误经常见!                if (!inPre &&                    !vnode.ns &&                    !(config.ignoredElements.length && config.ignoredElements.indexOf(tag) > -1) &&                    config.isUnknownElement(tag)                ) {                    warn(                        'Unknown custom element: <' + tag + '> - did you ' +                        'register the component correctly? For recursive components, ' +                        'make sure to provide the "name" option.',                        vnode.context                    );                }            }            // ns???            vnode.elm = vnode.ns ?                nodeOps.createElementNS(vnode.ns, tag) :                nodeOps.createElement(tag, vnode);            setScope(vnode);            /* istanbul ignore if */            {                createChildren(vnode, children, insertedVnodeQueue);                if (isDef(data)) {                    invokeCreateHooks(vnode, insertedVnodeQueue);                }                insert(parentElm, vnode.elm, refElm);            }            if ("development" !== 'production' && data && data.pre) {                inPre--;            }        }        // 注释         else if (isTrue(vnode.isComment)) {            vnode.elm = nodeOps.createComment(vnode.text);            insert(parentElm, vnode.elm, refElm);        }        // 处理纯文本        else {            vnode.elm = nodeOps.createTextNode(vnode.text);            insert(parentElm, vnode.elm, refElm);        }    }

  函数进入就会调用createComponent进行判断,这个函数暂时看不太懂,一个分支都没有进,直接返回了undefined:

// line-4855    function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {        // 获取属性        var i = vnode.data;        if (isDef(i)) {            var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;            if (isDef(i = i.hook) && isDef(i = i.init)) {                i(vnode, false /* hydrating */ , parentElm, refElm);            }            // 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(vnode.componentInstance)) {                initComponent(vnode, insertedVnodeQueue);                if (isTrue(isReactivated)) {                    reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);                }                return true            }        }    }

  下一步是跳到createElement函数,nodeOps中的方法都是自封装的DOM操作方法,超级简单。

// line-4599    function createElement$1(tagName, vnode) {        var elm = document.createElement(tagName);        if (tagName !== 'select') {            return elm        }        // 对select做特殊处理        // false or null will remove the attribute but undefined will not        if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {            elm.setAttribute('multiple', 'multiple');        }        return elm    }

  可以看,这个方法也就是创建一个DOM节点并返回,对select会做特殊处理,于是vnode.elm也就是个普通的DOM节点。

 

  接下来是setScope函数(),该函数目的是给根节点添加一个特殊的ID,避免css样式影响其他模块。

  这个东西我就熟悉啦,触发形式是这样:

  渲染结果是这样:

  这样可以保证所有的样式都是独立的,就算名字一样,后面添加的随机字符串可以保证唯一。

// line-4958    function setScope(vnode) {        var i;        var ancestor = vnode;        while (ancestor) {            if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {                nodeOps.setAttribute(vnode.elm, i, '');            }            ancestor = ancestor.parent;        }        // for slot content they should also get the scopeId from the host instance.        if (isDef(i = activeInstance) &&            i !== vnode.context &&            isDef(i = i.$options._scopeId)) {            nodeOps.setAttribute(vnode.elm, i, '');        }    }

  函数判断虚拟DOM是否存在_scopeId,然后对每一个节点添加对应的Id,这里没有,所以就会跳过去。

  

  接下来调用createChildren方法,从名字也能看出来是对子节点的处理。实际上,也是对子节点或者纯文本进行递归处理:

// line-4927    function createChildren(vnode, children, insertedVnodeQueue) {        // 存在复杂子节点        if (Array.isArray(children)) {            for (var i = 0; i < children.length; ++i) {                createElm(children[i], insertedVnodeQueue, vnode.elm, null, true);            }        }        // 子节点为纯文本        else if (isPrimitive(vnode.text)) {            nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text));        }    }

  第一条分支处理DOM子节点,直接递归。本例子节点只是一个纯文本:

  因此,重新跳入createElm方法后,会直接进入第三个分支,即:

// line-4802    function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {        // 属性获取        // ...        if (isDef(tag)) {            // 处理节点类型        } else if (isTrue(vnode.isComment)) {            // 处理注释        } else {            vnode.elm = nodeOps.createTextNode(vnode.text);            insert(parentElm, vnode.elm, refElm);        }    }

  这个分支只有两条语句,第一条会创建一个文本节点给elm属性,第二条负责插入。

  第一个DOM操作只是简单调用document.createTextNode方法,直接看第二个函数吧:

// line-4915    // parent => div => 父节点    // elm => #text => 本节点    // ref => null => 相邻节点    function insert(parent, elm, ref) {        if (isDef(parent)) {            if (isDef(ref)) {                if (ref.parentNode === parent) {                    //                     nodeOps.insertBefore(parent, elm, ref);                }            }             // parent.appendChild(elm)            else {                nodeOps.appendChild(parent, elm);            }        }    }

  第二个也是DOM操作!好吧,比较简单,看看就懂了。

  

  子节点处理完后,接下来处理本节点的属性,相关函数为invokeCreateHooks:

// line-4944    // vnode => 虚拟DOM    // insertedVnodeQueue => []    function invokeCreateHooks(vnode, insertedVnodeQueue) {        // cbs为工具对象        for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {            cbs.create[i$1](emptyNode, vnode);        }        i = vnode.data.hook; // Reuse variable        if (isDef(i)) {            if (isDef(i.create)) {                i.create(emptyNode, vnode);            }            if (isDef(i.insert)) {                insertedVnodeQueue.push(vnode);            }        }    }

  比较重要的是这里的cbs对象,在外部函数运行最开始有进行初始化,如下:

// line-4762    function createPatchFunction(backend) {        var i, j;        var cbs = {};        // line-6968        // var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });        var modules = backend.modules;        var nodeOps = backend.nodeOps;        for (i = 0; i < hooks.length; ++i) {            cbs[hooks[i]] = [];            for (j = 0; j < modules.length; ++j) {                if (isDef(modules[j][hooks[i]])) {                    cbs[hooks[i]].push(modules[j][hooks[i]]);                }            }        }        // tools fn        // return fn    }

  这个对象来源于多个对象的拼接,包含大量虚拟DOM的操作方法,内容如图:

  从名字可见,该对象整合了创建、摧毁、移除、更新等功能库,此处仅用create来创建,内部函数如图:

  invokeCreateHooks函数一开始遍会遍历此数组,依次传参执行,参数为空vnode与当前的vnode。

 

  这部分比较长,下次再讲。

  

转载于:https://www.cnblogs.com/QH-Jimmy/p/7207242.html

你可能感兴趣的文章
k8s架构
查看>>
select 向上弹起
查看>>
mysql 多表管理修改
查看>>
group by order by
查看>>
bzoj 5252: [2018多省省队联测]林克卡特树
查看>>
https 学习笔记三
查看>>
Oracle学习之简单查询
查看>>
log4j配置
查看>>
linux 配置SAN存储-IPSAN
查看>>
双链表
查看>>
java学习笔记之String类
查看>>
pymysql操作mysql
查看>>
Linux服务器删除乱码文件/文件夹的方法
查看>>
牛腩记账本core版本源码
查看>>
Word Break II
查看>>
UVA 11082 Matrix Decompressing 矩阵解压(最大流,经典)
查看>>
jdk从1.8降到jdk1.7失败
查看>>
一些关于IO流的问题
查看>>
mongo备份操作
查看>>
8 -- 深入使用Spring -- 3...1 Resource实现类InputStreamResource、ByteArrayResource
查看>>