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

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

怎么感觉遥遥无期了呀~这个源码,跑不完了。

这个系列写的不好,仅作为一个记录,善始善终,反正也没人看,写着玩吧!

 

  接着上一节的cbs,这个对象在初始化应该只会调用create模块数组方法,简单回顾一下到哪了。

// line-4944    function invokeCreateHooks(vnode, insertedVnodeQueue) {        // 遍历调用数组方法        // emptyNode => 空虚拟DOM        // vnode => 当前挂载的虚拟DOM        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.create中的方法:

一、updateAttrs

  前面是对vnode的attrs进行更新,__ob__属性代表该对象被观测,可能会变动,后面是对旧vnode属性的移除。

// line-5456    // 因为是初始化    // 此时oldVnode是空的vnode    function updateAttrs(oldVnode, vnode) {        // 老、新vnode都没属性就返回        if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {            return        }        var key, cur, old;        var elm = vnode.elm;        var oldAttrs = oldVnode.data.attrs || {};        var attrs = vnode.data.attrs || {};        // __ob__属性代表可能变动        if (isDef(attrs.__ob__)) {            attrs = vnode.data.attrs = extend({}, attrs);        }        // attrs => {id:app}        for (key in attrs) {            cur = attrs[key];            old = oldAttrs[key];            // 更改属性            if (old !== cur) {                setAttr(elm, key, cur);            }        }        // IE9        if (isIE9 && attrs.value !== oldAttrs.value) {            setAttr(elm, 'value', attrs.value);        }        // 移除旧vnode的属性        for (key in oldAttrs) {            if (isUndef(attrs[key])) {                if (isXlink(key)) {                    elm.removeAttributeNS(xlinkNS, getXlinkProp(key));                } else if (!isEnumeratedAttr(key)) {                    elm.removeAttribute(key);                }            }        }    }

  这里主要是最后遍历新vnode的属性,调用setAttr进行设置。

// line-5492    // el => div    // key => id     // value => app    function setAttr(el, key, value) {        if (isBooleanAttr(key)) {            // 处理无值属性 如:disabled             if (isFalsyAttrValue(value)) {                el.removeAttribute(key);            } else {                el.setAttribute(key, key);            }        } else if (isEnumeratedAttr(key)) {            el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true');        } else if (isXlink(key)) {            if (isFalsyAttrValue(value)) {                el.removeAttributeNS(xlinkNS, getXlinkProp(key));            } else {                el.setAttributeNS(xlinkNS, key, value);            }        } else {            if (isFalsyAttrValue(value)) {                el.removeAttribute(key);            } else {                el.setAttribute(key, value);            }        }    }    var isBooleanAttr = makeMap(        'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +        'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +        'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' +        'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' +        'required,reversed,scoped,seamless,selected,sortable,translate,' +        'truespeed,typemustmatch,visible'    );    var isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck');    var isXlink = function(name) {        // 以xlink:开头的字符串        return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'    };    var isFalsyAttrValue = function(val) {        return val == null || val === false    };

  函数看似判断很多很复杂,其实很简单,只是根据属性的类别做处理。本例中,直接会跳到最后的setAttribute,直接调用原生方法设置属性,结果就是给div设置了id:app属性。

  因为oldVnode是空的,所以没有属性可以移除。

 

二、updateClass

  这个从名字看就明白了,就是类名更换而已。

// line-5525    function updateClass(oldVnode, vnode) {        var el = vnode.elm;        var data = vnode.data;        var oldData = oldVnode.data;        // 是否有定义class属性        if (            isUndef(data.staticClass) &&            isUndef(data.class) && (                isUndef(oldData) || (                    isUndef(oldData.staticClass) &&                    isUndef(oldData.class)                )            )        ) {            return        }        var cls = genClassForVnode(vnode);        // handle transition classes        var transitionClass = el._transitionClasses;        if (isDef(transitionClass)) {            cls = concat(cls, stringifyClass(transitionClass));        }        // set the class        if (cls !== el._prevClass) {            el.setAttribute('class', cls);            el._prevClass = cls;        }    }

  因为没有class,所有会直接返回了。

 

三、updateDOMListeners

  这个是更新事件监听

// line-6156    function updateDOMListeners(oldVnode, vnode) {        if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {            return        }        var on = vnode.data.on || {};        var oldOn = oldVnode.data.on || {};        target$1 = vnode.elm;        normalizeEvents(on);        updateListeners(on, oldOn, add$1, remove$2, vnode.context);    }

  很明显,本例中也没有事件,所以跳过

 

四、updateDOMProps

  这个是更新组件的props值

// line-6174    function updateDOMProps(oldVnode, vnode) {        if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {            return        }        // code    }

  因为代码太长又不执行,所以简单跳过。

 

五、updateStyle

  更新style属性~

// line-6364    function updateStyle(oldVnode, vnode) {        var data = vnode.data;        var oldData = oldVnode.data;        if (isUndef(data.staticStyle) && isUndef(data.style) &&            isUndef(oldData.staticStyle) && isUndef(oldData.style)) {            return        }        // 跳过...    }

 

六、_enter

  这个enter函数有点长,但是直接return,所以具体代码就不贴出来了。

// line-6934    function _enter(_, vnode) {        // 第一参数没有用        if (vnode.data.show !== true) {            enter(vnode);        }    }

 

七、create

// line-4674    create: function create(_, vnode) {        registerRef(vnode);    }
// line-4688    function registerRef(vnode, isRemoval) {        var key = vnode.data.ref;        if (!key) {            return        }        // return了    }

  这个也return了。

 

八、updateDirectives

  从名字来看是更新组件没错了!

// line-5346    function updateDirectives(oldVnode, vnode) {        if (oldVnode.data.directives || vnode.data.directives) {            _update(oldVnode, vnode);        }    }

  先判断新旧节点是否有directives属性,没有直接跳过。

 

  到这里,8个初始化方法全部调用完毕,函数返回createElm:

// line-4802    function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {        // 获取属性        // ...        if (isDef(tag)) {            // warning...            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);        }    }

  经过createChildren后,vnode的elm属性,也就是原生DOM会被添加各种属性,然后进入insert函数。

  insert函数接受3个参数,父节点、当前节点、相邻节点,本例中对应body、div、空白文本节点。

// line-4915    // parent => body    // elm => vnode.elm => div    // ref => #text    function insert(parent, elm, ref) {        if (isDef(parent)) {            if (isDef(ref)) {                if (ref.parentNode === parent) {                    nodeOps.insertBefore(parent, elm, ref);                }            } else {                nodeOps.appendChild(parent, elm);            }        }    }

  这个函数前面也见过,简单说一下,三个参数,父、当前、相邻。

  1、如果都存在,调用insertBefore将当前插入相邻前。

  2、如果没有相邻节点,直接调用appendChild把节点插入父。

  这个调用完,页面终于惊喜的出现了变化!

  patch函数一阶段完成,页面已经插入的对应的DOM节点。

 

  返回后,进入第二阶段,移除那个{

{message}}:

// line-5250    function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {        // isUndef(vnode)...        var isInitialPatch = false;        var insertedVnodeQueue = [];        if (isUndef(oldVnode)) {            // ...        } else {            var isRealElement = isDef(oldVnode.nodeType);            if (!isRealElement && sameVnode(oldVnode, vnode)) {                // patch existing root node                patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);            } else {                if (isRealElement) {                    // SSR                    // hydrating                    oldVnode = emptyNodeAt(oldVnode);                }                var oldElm = oldVnode.elm;                var parentElm$1 = nodeOps.parentNode(oldElm);                // createElm => 生成原生DOM节点并插入页面                if (isDef(vnode.parent)) {                    // ...                }                // go!                if (isDef(parentElm$1)) {                    removeVnodes(parentElm$1, [oldVnode], 0, 0);                } else if (isDef(oldVnode.tag)) {                    invokeDestroyHook(oldVnode);                }            }        }        invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);        return vnode.elm    }

  接下里会进入removeVnodes函数对模板样式进行移除,至于另外分支是对旧vnode移除,不属于页面初始化阶段。

// line-4995    // parentElm => body    // vnodes => 挂载虚拟DOM集合    // startIdx => endIdx =>1    function removeVnodes(parentElm, vnodes, startIdx, endIdx) {        for (; startIdx <= endIdx; ++startIdx) {            var ch = vnodes[startIdx];            if (isDef(ch)) {                if (isDef(ch.tag)) {                    removeAndInvokeRemoveHook(ch);                    invokeDestroyHook(ch);                } else { // Text node                    removeNode(ch.elm);                }            }        }    }

  函数很简洁,一个分支处理DOM节点,一个分支处理文本节点。本例中进入第一个分支,将移除挂载的DOM节点。

  有两个方法处理移除DOM节点:

1、removeAndInvokeRemoveHook

// line-5009    function removeAndInvokeRemoveHook(vnode, rm) {        if (isDef(rm) || isDef(vnode.data)) {            var i;            var listeners = cbs.remove.length + 1;            if (isDef(rm)) {                rm.listeners += listeners;            } else {                // 返回一个函数                rm = createRmCb(vnode.elm, listeners);            }            // recursively invoke hooks on child component root node            if (isDef(i = vnode.componentInstance) && 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 {            removeNode(vnode.elm);        }    }    // line-4782    function createRmCb(childElm, listeners) {        // 这是rm        function remove$$1() {            if (--remove$$1.listeners === 0) {                removeNode(childElm);            }        }        remove$$1.listeners = listeners;        return remove$$1    }    // line-6934    remove: function remove$$1(vnode, rm) {        /* istanbul ignore else */        if (vnode.data.show !== true) {            // 由于没有data属性 直接返回vm()            leave(vnode, rm);        } else {            rm();        }    }

  这个方法非常绕,处理的东西很多,然而本例只有一个DOM节点需要移除,所以跳过很多地方,直接执行rm()函数。

  即rm => removeNode() => parentNode.removeChild()。

  最后成功移除原有的div。

 

  但是没完,移除DOM节点后,还需要处理vnode,于是进行第二步invokeDestroyHook:

// line-4981    function invokeDestroyHook(vnode) {        var i, j;        var data = vnode.data;        if (isDef(data)) {            // destroy钩子函数?            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]);            }        }    }

  这里与上面的cbs.create类似,也是遍历调用对应的数组方法,此处为destroy。

1、destroy

  这里没有ref属性,直接返回了。

// line-4683    destroy: function destroy(vnode) {        registerRef(vnode, true);    }    function registerRef(vnode, isRemoval) {        var key = vnode.data.ref;        if (!key) {            return        }        // more    }

2、unbindDirectives

  直接返回了。

// line-5341    destroy: function unbindDirectives(vnode) {        updateDirectives(vnode, emptyNode);    }    function updateDirectives(oldVnode, vnode) {        if (oldVnode.data.directives || vnode.data.directives) {            _update(oldVnode, vnode);        }    }

  

  后面也没有什么,直接返回到patch函数进行最后一步。

// line-5341    function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {        // 插入移除节点        invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);        return vnode.elm    }

  这里的invokeInsertHook处理钩子函数的插入,由于不存在,所以也直接返回了。

  

  接下来会一直返回到mountComponent方法:

// line-2374    function mountComponent(vm, el, hydrating) {        vm.$el = el;        // warning        callHook(vm, 'beforeMount');        var updateComponent;        if ("development" !== 'production' && config.performance && mark) {            // warning        } else {            updateComponent = function() {                // 渲染DOM                vm._update(vm._render(), hydrating);            };        }        vm._watcher = new Watcher(vm, updateComponent, noop);        hydrating = false;        // 调用mounted钩子函数         if (vm.$vnode == null) {            vm._isMounted = true;            callHook(vm, 'mounted');        }        return vm    }

  最后调用钩子函数mounted,返回vue实例。

 

  到此,流程全部跑完了。

  啊~不容易。

  

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

你可能感兴趣的文章