前言

上一篇文章中分析了render函数生成的主要细节,实际上主要是parse + generate解析template构建render函数的过程。当render函数构建过后,接下来就生成一个watcher实例,该实例与视图渲染密切相关,会触发render函数执行。

实际上VNode对象的创建是在render调用过程中触发的,这是render函数本身的结构导致的。本文主要分析render调用过程中vnode的创建。

具体分析

在render构建和调用那篇文章中,就提及render的调用实际上是Watcher实例对象创建触发的,主要的处理逻辑如下:
在这里插入图片描述

从上图的主要逻辑点可以看出,$mount的处理过程中主要会构建Watcher实例,而Watcher实例最终会执行Vue实例方法_update和_render,这里是核心了。

_render实例方法

_render实例方法实际上主要的处理点就是执行render函数返回其执行结果,核心代码逻辑如下:

var vnode = render.call(vm._renderProxy, vm.$createElement);
return vnode;

在上一篇文章中,知悉了render函数的基本构造,还使用如下实例:

<div>
	{{ text }}
</div>

得到的render函数如下:

const render = new Function("with(this) {return _c('div', {attrs: {'id': 'app'}}, [_v('\n' + _s(text) + '\n)])}")

在执行_render函数实际上就是执行render函数,此时会调用:

vm._c:即$createElement
vm._s:即toString函数
vm.text:即触发data响应式,会调用getter函数,获取text最新的值

_s实例方法

_s实例方法的源码如下:

function toString(val) {
	return val === null 
				? ' ' 
				: typeof val === 'object' 
					// 格式化对象,并指定缩进为2个空格
					? JSON.stringify(val, null, 2) 
					: String(val);
}

主要就是处理null、对象以及数组形式的数据等将其转换为字符串。

_v实例方法
function createTextVNode(val) {
	return new VNode(undefined, undefined, undefined, String(val));
}

_v实际上就是createTextVNode函数,用于创建文本类型的虚拟DOM

_c实例方法

该方法是vnode创建的实际出发点,Vue核心方法之一,具体源码如下:

vm._c = function(a, b, c, d) {
	return createElement(vm, a, b, c, d, false);
};

createElement整个的处理逻辑如下:
在这里插入图片描述

上图是根据最基本的实例梳理的主要脉络,核心是调用VNode构造函数创建虚拟DOM:

new VNode(tag, data, children, undefined, undefined, context)

实际上HTML标签、SVG标签、自定义组件都是通过createElement来生成对应的Vnode对象的。

这里需要说明下createElement中a、b、c、d参数表示的含义:

  • a:tag,表示标签名
  • b:data,表示属性、事件、class、props等的配置对象
  • c: children,表示子节点
  • d:normalizationType,表示类型,即要如何处理children中的数据

实际上对于自定义组件这里的逻辑就需要具体聊聊了,creaElement关于这边的逻辑如下:

if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
	// component
	vnode = createComponent(Ctor, data, context, children, tag);
}

而createComponent函数的逻辑自然是处理与组件相关的属性例如props、事件等从而创建Vnode对象,但是其中有一个重要的逻辑是installComponentHooks。

自定义组件对应的虚拟节点VNode对象有一些Hooks(钩子函数):

var componentVNodeHooks = {
  init: function init() {},
  prepatch: function prepatch(oldVnode, vnode) {},
  insert: function insert(vnode) {},
  destroy: function destroy(vnode) {}
};

实际上定义初始化、patch之前、插入、销毁几个阶段的钩子,这些钩子是用于patch阶段用来创建、更新、销毁组件的。

实际上这里还隐藏了一个非常重要的逻辑信息:

所有通过createElement创建的组件都是子组件,而所有子组件在render函数执行时只是创建了虚拟节点VNode对象而已。每一个组件都是一个Vue实例,子组件的Vue实例创建是在patchVnode(即diff算法)阶段才生成的

VNode构造函数

VNode构造函数实际上就是定义相关属性,VNode中重要的属性有:

  • tag:当前标签名
  • data:标签属性、props、事件等对象集合
  • children:子节点的VNode数组
  • text:当前标签文本内容
  • context:上下文对象,即Vue实例对象
var VNode = function(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
	this.tag = tag;
	this.data = data;
	this.children = children;
	...
}

简单实例_render函数的处理就是上面整个逻辑:

_render() -> render -> createElement(或createTextVNode)-> new VNode

_render执行render函数生成DOM结构的vnode,接下来就是_update实例方法的调用了。

_update实例方法

_update中处理实际上有两个主要点:

  • vm._vnode相关处理,两点:prevNode = vm._vnode,vm._vnode = vnode
  • vm.__patch__的调用

prevNode记录更新前的vnode,如果是初始化,那么prevNode就是空,调用__patch__实现vnode -> html的过程,也是diff算法的实现过程,是整个Vue中核心点之一。

_update核心源码如下:

var prevNode = vm._vnode;
vm._vnode = vnode;
if (!prevNode) {
	// 初始化
	vm.$el = vm.__patch__(vm.$el,vnode, hydrating, false);
} else {
	// 更新
	vm.$el = vm.__patch__(prevNode, vnode);
}

总结

render函数的结构:

with(this) {
	// code
}

this === Vue实例,这个结构内部对相关属性的使用都会从Vue实例中查找,而Vue响应式就是通过属性触发的。
render函数执行过程中_v、_c函数调用会触发组件或原生标签对应虚拟节点VNode的生成,而响应式data数据也是render函数调用触发的。而_update实例函数就负责基于VNode创建真实DOM,通过diff算法最大优化DOM复用。

Logo

前往低代码交流专区

更多推荐