根据调试工具看Vue源码之虚拟dom(二)
栏目: JavaScript · 发布时间: 5年前
内容简介:上回我们提到,在子组件存在的情况下,父组件在执行完在上一篇与之相关的代码:point_down:
前言
上回我们提到,在子组件存在的情况下,父组件在执行完 created
钩子函数之后生成子组件的实例,子组件执行 created
钩子函数,同时也检查是否也有子组件,有则重复父组件的步骤,否则子组件的 dom
元素渲染
深入了解 vnode
在上一篇 文章
中其实我们提到一个函数 —— createComponentInstanceForVnode
:point_down:
function createComponentInstanceForVnode ( vnode, // we know it's MountedComponentVNode but flow doesn't parent // activeInstance in lifecycle state ) { var options = { _isComponent: true, _parentVnode: vnode, parent: parent }; // check inline-template render functions var inlineTemplate = vnode.data.inlineTemplate; if (isDef(inlineTemplate)) { options.render = inlineTemplate.render; options.staticRenderFns = inlineTemplate.staticRenderFns; } return new vnode.componentOptions.Ctor(options) }
与之相关的代码:point_down:
... var child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ); child.$mount(hydrating ? vnode.elm : undefined, hydrating);
从中我们可以得知:
createComponentInstanceForVnode vnode.componentOptions.Ctor(options)
VNode
通过全局检索 componentOptions
,可知存在如下代码:point_down:
var VNode = function VNode ( tag, data, children, text, elm, context, componentOptions, asyncFactory ) { this.tag = tag; this.data = data; this.children = children; this.text = text; this.elm = elm; ... }
实际上,在 beforeMount
钩子和 mounted
钩子之间,有段奇怪的代码:sweat_smile:
new Watcher(vm, updateComponent, noop, { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true /* isRenderWatcher */);
看过前面的文章的你其实已经知道 Watcher
的执行逻辑:
-
初始化相关属性,其中包括
getter
属性 -
对
value
赋值的同时执行getter
附 updateComponent
实现:
updateComponent = function () { vm._update(vm._render(), hydrating); };
这意味着函数 updateComponent
将被执行,同时存在这样的调用顺序(从上往下执行):
vm._render vm_update
同时 dom
元素肯定也是在这两个函数调用时渲染
vm._render
Vue.prototype._render = function () { var vm = this; var ref = vm.$options; var render = ref.render; var _parentVnode = ref._parentVnode; if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ); } // set parent vnode. this allows render functions to have access // to the data on the placeholder node. vm.$vnode = _parentVnode; // render self var vnode; try { // There's no need to maintain a stack becaues all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm; vnode = render.call(vm._renderProxy, vm.$createElement); } catch (e) { handleError(e, vm, "render"); // return error render result, // or previous vnode to prevent render error causing blank component /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e); } catch (e) { handleError(e, vm, "renderError"); vnode = vm._vnode; } } else { vnode = vm._vnode; } } finally { currentRenderingInstance = null; } // if the returned array contains only a single node, allow it if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0]; } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ); } vnode = createEmptyVNode(); } // set parent vnode.parent = _parentVnode; return vnode };
简单梳理下函数 _render
的执行过程(从上往下):
-
将
_parentVnode
(父组件的vnode
) 赋值给vm.$vnode
-
执行
normallizeScopedSlots
,将父子组件的$slots
跟$scopedSlots
合并 -
执行
render
函数并赋值给vnode
(即得到现有的vnode
) -
如果
vnode
为空则执行createEmptyVNode
函数 -
返回
vnode
这里我们优先把断点打入 render
函数,理所当然的会得到以下执行过程:
render vm.$createElement
由于最先执行的是 new Vue({...})
,所以看上去断点好像停在了「奇怪的地方」:point_down:
new Vue({ render: h => h(App), }).$mount('#app')
render
函数
细心的同学会注意到这样一行代码:point_down:
vnode = render.call(vm._renderProxy, vm.$createElement);
步进之后断点马上跳到了这里:point_down:
new Vue({ render: h => h(App), ... }).$mount('#app')
其实,这里将 vm._renderProxy
作为了 render
函数的上下文对象,而 vm.$createElement
返回一个闭包函数作为 render
函数的参数传入
相关代码:
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
总结
总结下生成 vnode
的完整逻辑:
-
执行
$mount
函数 -
判断是否在浏览器环境下,是则获取
dom
元素并赋值给el
变量,否则el
变量取值undefined
-
执行
mountComponent
函数 -
执行
new Watcher(vm, updateComponent, noop, ...)
-
由于
Watcher
的「特性」(传入的updateComponent
赋值给getter
之后执行),_render
函数在这之后会被触发 -
执行
$createElement
-
执行
createElement
-
执行
_createElement
-
判断参数
data
及data.is
是否不为空,是则将data.is
赋值给tag
-
如果
tag
为空,那么认为这是一个空白节点,此时调用createEmptyVNode
创建一个「空白节点」,并把isComment
标记为true
-
判断
tag
是否是「保留字」,是则属于HTML
标签,生成对应的vnode
,否则调用createComponent
函数生成对应的vnode
-
最后返回
vnode
相关函数
createEmptyVNode
var createEmptyVNode = function (text) { if ( text === void 0 ) text = ''; var node = new VNode(); node.text = text; node.isComment = true; return node };
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Golang源码探索(一) 编译和调试源码
- 使用gdb调试工具上手调试php和swoole源码
- JVM源码分析-JVM源码编译与调试
- 调试 Flink 源码
- Node.js 源码调试
- 如何断点调试Tomcat源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。