vue源码解读-dom挂载1

vue框架是一款双向数据绑定的框架,能大大提高我们的开发效率。那么vue是如何将模板生成dom的呢,通过阅读源码可以知道这一过程。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    {{ message }}
</div>
</body>
<script src="vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            message: 'Hello Vue!'
        }
    })
</script>
</html>

如上是一个简单的使用vue的例子。可以知道通过new Vue()生成vue实例。
vue构造器如下:

  function Vue (options) {
        if ("development" !== 'production' &&
            !(this instanceof Vue)
        ) {
            warn('Vue is a constructor and should be called with the `new` keyword');
        }
        this._init(options);//初始化
    }
    
    //_init为原型方法
    Vue.prototype._init = function (options) {
        var vm = this;
        // a uid
        vm._uid = uid$3++;

        var startTag, endTag;
        /* istanbul ignore if */
        if ("development" !== 'production' && config.performance && mark) {
            startTag = "vue-perf-start:" + (vm._uid);
            endTag = "vue-perf-end:" + (vm._uid);
            mark(startTag);
        }

        // a flag to avoid this being observed
        vm._isVue = true;
        // merge options
        if (options && options._isComponent) {
            // optimize internal component instantiation
            // since dynamic options merging is pretty slow, and none of the
            // internal component options needs special treatment.
            initInternalComponent(vm, options);
        } else {
            vm.$options = mergeOptions(
                resolveConstructorOptions(vm.constructor),
                options || {},
                vm
            );
        }
        /* istanbul ignore else */
        {
            initProxy(vm);
        }
        // expose real self
        vm._self = vm;
        initLifecycle(vm);
        initEvents(vm);
        initRender(vm);
        callHook(vm, 'beforeCreate');
        initInjections(vm); // resolve injections before data/props
        initState(vm);
        initProvide(vm); // resolve provide after data/props
        callHook(vm, 'created');

        /* istanbul ignore if */
        if ("development" !== 'production' && config.performance && mark) {
            vm._name = formatComponentName(vm, false);
            mark(endTag);
            measure(("vue " + (vm._name) + " init"), startTag, endTag);
        }

        if (vm.$options.el) {
            vm.$mount(vm.$options.el); //挂载方法
        }
    };

可以看出通过vm. m o u n t d o m mount挂载dom。那么这个 mount是如何实现的呢,在源码中有2处实现$mount

//8522 line
Vue.prototype.$mount = function (
    el,
    hydrating
) {
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
};
//10860 line
var mount = Vue.prototype.$mount; //用变量保存之前的$mount
Vue.prototype.$mount = function ( //重写$mount 实际上vue实例调用的是该方法
    el,
    hydrating
) {
    el = el && query(el);

    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
        "development" !== 'production' && warn(
            "Do not mount Vue to <html> or <body> - mount to normal elements instead."
        );
        return this
    }

    var options = this.$options;
    // resolve template/el and convert to render function
    if (!options.render) {
        var template = options.template;
        if (template) {
            if (typeof template === 'string') {
                if (template.charAt(0) === '#') {
                    template = idToTemplate(template);
                    /* istanbul ignore if */
                    if ("development" !== 'production' && !template) {
                        warn(
                            ("Template element not found or is empty: " + (options.template)),
                            this
                        );
                    }
                }
            } else if (template.nodeType) {
                template = template.innerHTML;
            } else {
                {
                    warn('invalid template option:' + template, this);
                }
                return this
            }
        } else if (el) {
            template = getOuterHTML(el);
        }
        if (template) {
            /* istanbul ignore if */
            if ("development" !== 'production' && config.performance && mark) {
                mark('compile');
            }

            var ref = compileToFunctions(template, {
                shouldDecodeNewlines: shouldDecodeNewlines,
                shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
                delimiters: options.delimiters,
                comments: options.comments
            }, this);
            var render = ref.render;
            var staticRenderFns = ref.staticRenderFns;
            options.render = render;
            options.staticRenderFns = staticRenderFns;

            /* istanbul ignore if */
            if ("development" !== 'production' && config.performance && mark) {
                mark('compile end');
                measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
            }
        }
    }
    return mount.call(this, el, hydrating)//调用被覆盖的那个$mount
};

通过阅读以上代码我们知道实际上底层调用的挂载方法是mountComponent

function mountComponent (
    vm,
    el,
    hydrating
) {
    vm.$el = el;
    if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode;
        {
            /* istanbul ignore if */
            if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
                vm.$options.el || el) {
                warn(
                    'You are using the runtime-only build of Vue where the template ' +
                    'compiler is not available. Either pre-compile the templates into ' +
                    'render functions, or use the compiler-included build.',
                    vm
                );
            } else {
                warn(
                    'Failed to mount component: template or render function not defined.',
                    vm
                );
            }
        }
    }
    callHook(vm, 'beforeMount');

var updateComponent;
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
    updateComponent = function () {
        var name = vm._name;
        var id = vm._uid;
        var startTag = "vue-perf-start:" + id;
        var endTag = "vue-perf-end:" + id;

        mark(startTag);
        var vnode = vm._render();
        mark(endTag);
        measure(("vue " + name + " render"), startTag, endTag);

        mark(startTag);
        vm._update(vnode, hydrating);
        mark(endTag);
        measure(("vue " + name + " patch"), startTag, endTag);
    };
} else {
    updateComponent = function () {
        vm._update(vm._render(), hydrating);//该方法为更新dom的方法
    };
}

// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);//通过传入updateComponent。在生成Watcher的时候会调用该方法。
hydrating = false;

// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
}
return vm
}

以上代码主要定义生成一个watcher传入updateComponent实现更新dom。继续查看_update的实现:

Vue.prototype._update = function (vnode, hydrating) {
            var vm = this;
            if (vm._isMounted) {
                callHook(vm, 'beforeUpdate');
            }
            var prevEl = vm.$el;
            var prevVnode = vm._vnode;
            var prevActiveInstance = activeInstance;
            activeInstance = vm;
            vm._vnode = vnode;
            // Vue.prototype.__patch__ is injected in entry points
            // based on the rendering backend used.
            if (!prevVnode) { //第一次插入时调用的方法
                // initial render
                vm.$el = vm.__patch__(
                    vm.$el, vnode, hydrating, false /* removeOnly */,
                    vm.$options._parentElm,
                    vm.$options._refElm
                );
                // no need for the ref nodes after initial patch
                // this prevents keeping a detached DOM tree in memory (#5851)
                vm.$options._parentElm = vm.$options._refElm = null;
            } else {//更新方法
                // updates
                vm.$el = vm.__patch__(prevVnode, vnode);//比较vnode差别以及更新挂载dom
            }
            activeInstance = prevActiveInstance;
            // update __vue__ reference
            if (prevEl) {
                prevEl.__vue__ = null;
            }
            if (vm.$el) {
                vm.$el.__vue__ = vm;
            }
            // if parent is an HOC, update its $el as well
            if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
                vm.$parent.$el = vm.$el;
            }
            // updated hook is called by the scheduler to ensure that children are
            // updated in a parent's updated hook.
        };

可以知道最终通过__patch__去比较vnode的差别以及去更新挂载dom的

猜你喜欢

转载自blog.csdn.net/qq_33203555/article/details/82761012