初次探索什么是虚拟domVue 通过建立一个虚拟 DOM 对真实 DOM 发生的变化保持追踪。请仔细看这行代码:return createElement(‘h1’, this.blogTitle)createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,及其子节点。我们把这样的节点描述为“虚拟节点 (Virtual Node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。以上这段对虚拟Dom的简短介绍来自Vue的官网第一个断点我们一开始的断点先打在app.vue的两个hook上:export default { name: ‘app’, created () { debugger }, mounted () { debugger }}刷新页面,此时调用栈中显示的函数跟预想中的不太一样: 在created这个hook执行之前,多出了一些比较奇怪的函数:createComponentInstanceForVnodeVue._updatemountComponent????看完以后我心中出现了一个疑问:为什么在created钩子执行之前就出现了mountComponent这个方法,到底是文档出问题了,还是文档出问题了呢?带着这个疑惑我们接着往下看mountComponent做了什么?通过上面打第一个断点,其实不难看出这样的执行顺序(从上往下):(annoymous)Vue.$mountmountComponent(annoymous)这步其实就是在执行我们的main.js,代码很短:…new Vue({ render: h => h(App)}).$mount(’#app’)Vue.$mountVue.prototype.$mount = function ( el, hydrating) { // 判断是否处于浏览器的环境 el = el && inBrowser ? query(el) : undefined; // 执行mountComponent return mountComponent(this, el, hydrating)};mountComponentfunction mountComponent ( vm, el, hydrating) { vm.$el = el; if (!vm.$options.render) { vm.$options.render = createEmptyVNode; // 开发环境下给出警告提示 if (process.env.NODE_ENV !== ‘production’) { /* 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 / // 这里对测试环境跟正式环境的updateComponent 做了实现上的一个区分 if (process.env.NODE_ENV !== ‘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); }; } // 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, { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, ‘beforeUpdate’); } } }, true / isRenderWatcher /); 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}简单罗列下上面这两段代码的逻辑????:调用beforeMount钩子函数封装一个updateComponent函数执行new Watcher并将updateComponent当做参数传入调用vm._update方法_update方法是如何被触发的?Watchervar Watcher = function Watcher ( vm, expOrFn, cb, options, isRenderWatcher) { … // 将函数赋值给this.getter,这里是updateComponent函数 if (typeof expOrFn === ‘function’) { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = noop; process.env.NODE_ENV !== ‘production’ && warn( “Failed watching path: "” + expOrFn + “" " + ‘Watcher only accepts simple dot-delimited paths. ’ + ‘For full control, use a function instead.’, vm ); } } // 根据this.lazy决定是否触发get方法 this.value = this.lazy ? undefined : this.get();};Watcher.prototype.get = function get () { pushTarget(this); var value; var vm = this.vm; try { // 这里调用getter方法,实际上也就是调用updateComponent方法并拿到返回值 value = this.getter.call(vm, vm); } catch (e) { if (this.user) { handleError(e, vm, (“getter for watcher "” + (this.expression) + “"”)); } else { throw e } } finally { // “touch” every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value); } popTarget(); this.cleanupDeps(); } // 返回函数(updateComponent)执行结果 return value};简单梳理下上面这段代码的逻辑:新建Watcher实例时,将updateComponent赋值给getter属性通过this.get方法,触发updateComponent函数最终拿到函数的执行结果小结通过上面的分析我们可以初步得出一个结论:组件的渲染跟Watcher离不开关系,父组件在执行完created钩子函数之后,会调用updateComponent函数对子组件进行处理深入研究如果前面你动手跟着断点一直走,那么不难得知存在这样的调用关系(从上往下):…mountComponentWatchergetupdateComponentVue._updatepatchcreateElmcreateComponentinitcreateComponentInstanceForVnodeVueComponentVue._initcallHookinvokeWithErrorHandlingcreatedVue.prototype._updateVue.prototype._update = function (vnode, hydrating) { var vm = this; var prevEl = vm.$el; var prevVnode = vm._vnode; // 重存储当前父实例 var restoreActiveInstance = setActiveInstance(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 */); } else { // 执行patch函数 vm.$el = vm.patch(prevVnode, vnode); } restoreActiveInstance(); … };当然,我们通过全局检索可以得知_patch函数相关的代码????:// 只在浏览器环境下patch函数有效Vue.prototype.patch = inBrowser ? patch : noop;var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });function createPatchFunction (backend) { … return function patch (oldVnode, vnode, hydrating, removeOnly) { … }}这里先不深究patch的实现,我们只要知道patch是使用createPatchFunction来生成的一个闭包函数即可。子组件的渲染我们注意到,在子组件created钩子执行之前存在一个init方法????:var componentVNodeHooks = { init: function init (vnode, hydrating) { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch var mountedNode = vnode; // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode); } else { // 创建子组件实例 var child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ); // 对子组件执行$mount方法 child.$mount(hydrating ? vnode.elm : undefined, hydrating); } }, …相关代码:createComponentInstanceForVnodefunction createComponentInstanceForVnode ( vnode, // we know it’s MountedComponentVNode but flow doesn’t parent // activeInstance in lifecycle state) { // 初始化一个子组件的vnode配置 var options = { _isComponent: true, _parentVnode: vnode, parent: parent }; // 检查render函数内是否有template模板 var inlineTemplate = vnode.data.inlineTemplate; if (isDef(inlineTemplate)) { options.render = inlineTemplate.render; options.staticRenderFns = inlineTemplate.staticRenderFns; } // 返回子组件实例 return new vnode.componentOptions.Ctor(options)}总结存在子组件时,先初始化父组件,在created钩子执行之后,生成子组件的vnode实例子组件的created钩子执行完,检查子组件是否也有子组件子组件也存在子组件时,则重复1,否则直接执行$mount函数,渲染子组件