vue源码分析三 -- vm._render()如何生成虚拟dom

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue源码分析三 -- vm._render()如何生成虚拟dom相关的知识,希望对你有一定的参考价值。

参考技术A 我们在上一篇的最后讲解了vm._render是生成虚拟dom的关键,那么我们来看看他是如何生成的,下面是他的源码

createElement在文件../vdom/create-element里面,下面是他的源码

new Vnode 创建的是一个虚拟dom,其实就是一个装有很多属性的对象,和真实的dom做一个映射,目的是去渲染真实的dom,那么为什么不直接去渲染dom,因为vue中dom不仅有create的过程,还有diff,patch的过程。为了使得diff的过程花费的时间更短,虚拟dom就出来了,下面我们来看看new Vnode的源码

Vue源码后记-vFor列表渲染

  这一节肯定能完!

  

  经过DOM字符串的AST转化,再通过render变成vnode,最后就剩下patch到页面上了。

  render函数跑完应该是在这里:

    function mountComponent(vm, el, hydrating) {
        vm.$el = el;
        if (!vm.$options.render) {
            vm.$options.render = createEmptyVNode; {
                // warning
            }
        }
        // beforeMount

        var updateComponent;
        /* istanbul ignore if */
        if ("development" !== ‘production‘ && config.performance && mark) {
            updateComponent = function() {
                // dev render
            };
        } else {
            updateComponent = function() {
                vm._update(vm._render(), hydrating);
            };
        }

        // code...

        // mounted
        return vm
    }

  vm._render()会生成一个vnode看,接下来调用_update渲染页面,如下:

    Vue.prototype._update = function(vnode, hydrating) {
        var vm = this;
        // beforeUpdate

        // code...

        if (!prevVnode) {
            // initial render
            vm.$el = vm.__patch__(
                vm.$el, vnode, hydrating, false /* removeOnly */ ,
                vm.$options._parentElm,
                vm.$options._refElm
            );
        } else {
            // updates
            vm.$el = vm.__patch__(prevVnode, vnode);
        }

        // code...
    };

    Vue$3.prototype.__patch__ = inBrowser ? patch : noop;

    var patch = createPatchFunction({
        nodeOps: nodeOps,
        modules: modules
    });

    function createPatchFunction(backend) {
        var i, j;
        var cbs = {};

        var modules = backend.modules;
        var nodeOps = backend.nodeOps;

        for (i = 0; i < hooks.length; ++i) {
            // hook...
        }

        // fn...

        return function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
            // code...

            if (isUndef(oldVnode)) {
                // component...
            } else {
                var isRealElement = isDef(oldVnode.nodeType);
                if (!isRealElement && sameVnode(oldVnode, vnode)) {
                    // patch existing root node
                } else {
                    // SSR or hydrating

                    var oldElm = oldVnode.elm;
                    var parentElm$1 = nodeOps.parentNode(oldElm);
                    createElm(
                        vnode,
                        insertedVnodeQueue,
                        oldElm._leaveCb ? null : parentElm$1,
                        nodeOps.nextSibling(oldElm)
                    );

                    //code...
                }
            }

            invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
            return vnode.elm
        }
    }

  由于是初始化页面,所有在update的过程中,oldVNode被设置为空的div虚拟DOM,然后与生成的虚拟DOM进行替换。

  核心细节在上述代码中的createElm函数:

    // vnode => 生成的vnode
    // insertedVnodeQueue => []
    // parentElm => body
    // refElm => #text
    // 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)) {
            // pre...

            vnode.elm = vnode.ns ?
                nodeOps.createElementNS(vnode.ns, tag) :
                // 调用这个生成一个tag标签
                nodeOps.createElement(tag, vnode);
            setScope(vnode);

            {
                // 处理子节点
                // 子节点是5个vnode组成的数据 因此会循环调用本函数
                createChildren(vnode, children, insertedVnodeQueue);
                if (isDef(data)) {
                    // 生成DOM节点的属性
                    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);
        }
    }

  其实,这个普通的patch没有区别,只是由于是多个标签,所以会有兄弟元素,在插入节点会调用insertBefore进行插入,最后5个a标签依次插入生成的div,然后div插入body标签完成页面渲染。

  虽然循环生成a标签以及其属性比较麻烦,但是由于整个标签是一次性插入body中,所以对于性能也没有什么影响。

 

  完事,确实没什么好说的,至于v-if、v-show那些,有空一次性写完。

以上是关于vue源码分析三 -- vm._render()如何生成虚拟dom的主要内容,如果未能解决你的问题,请参考以下文章

2.4 ----vue render源码

彻底理解vue的patch流程和diff算法

Vue源码后记-vFor列表渲染

(尚049)Vue_源码分析_数据代理_效果+(尚050)Vue_源码分析_数据代理_效果_debug

Vue系列---理解Vue.nextTick使用及源码分析

Vue源码学习(六)- 实例方法