zoukankan      html  css  js  c++  java
  • .13-Vue源码之patch(3)(终于完事)

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

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

      接着上一节的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实例。

      到此,流程全部跑完了。

      啊~不容易。

      

  • 相关阅读:
    meta标签
    Vue(day8)
    Vue(day7)
    Vue(day6)
    Flex布局
    Vue(day5)
    jquery.data()&jquery.extend()
    Promise对象
    Vue(day4)
    Vue(day3)
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/7210363.html
Copyright © 2011-2022 走看看