new Vue() & JSX(二)

277 阅读3分钟

模板生成虚拟 VNode 的过程

上文说到在实例化渲染 watcher 时会调用 vm._render() 函数生成虚拟 VNode,然后调用 vm._update() 函数更新 DOM,接下来就分析一下 VNode 的生成过程,下一节再说一下 VNode 生成真实 DOM 的过程,部分代码就暂时不上图了!

vm._render() => VNode 流程:

执行 vm._render() 函数

1、vm._render() 函数就是 Vue.prototype._render() 函数定义在 Vue 的原型对象上。

2、在该函数内首先从 vm.$options 上获取 render()、_parentVnode 属性,而 _parentVnode 表示当前实例的父实例对应的 VNode 对象,然后又赋值 vm.$vnode = _parentVnode,则 vm.$vnode 也表示当前实例的父实例对应的 VNode 对象。

3、接着执行 vnode = render.call(vm, vm.$createElement) 函数并返回当前实例所对应的 VNode 对象。然后又进行校验 vnode instanceof VNode 是否成立,若不成立则变量 vnode 被赋值为一个空 VNode 对象。

4、最后执行 vnode.parent = _parentVnode; 建立父子 vnode 关系树,最后返回变量 vnode


重点分析一下 vm.$createElement() 函数

1、上文提到在执行 vnode = render.call(vm, vm.$createElement) 时传入参数 vm.$createElement() 函数,而我们发现在当前文件的 initRender() 函数内定义 vm.$createElement()/vm._c() 两个函数,并且两个函数都调用了 createElement() 函数,不同的是传参不一样,说明如下:

  • vm._c() 函数是为给模板被编译生成的 render() 函数所调用,定义如下:
vm._c = function(a,b,c,d) { createElement(vm,a,b,c,d,false) }
  • vm.$createElemen() 是为用户手写 render() 函数所调用,定义如下
vm._$createElemen = function(a,b,c,d) { createElement(vm,a,b,c,d,true) }
  • 按照官方文档来看,createElement() 函数只需要传入三个参数(第一个参数必填 String/Objetc/Function 类型,第二个为 Objetc 类型,第三个为 String/Array 类型,后两个参数可选),而源码中这个函数却定义了四个参数(a,b,c,d),从后续代码来看,第四个参数是为处理前三个参数而保留的,使 createElement(a,b,c,d) 函数更加灵活。

2、以下就是用户手写 render() 与框架模板编译生成 render() 函数的区别。通过多次调用 createElement() 函数生成对应的 VNode 对象。

3、回归上文,接着执行 createElement(vm,a,b,c,d,flag) 函数。首先进行判断若 b 为数组类型或原始数据类型,则将四个参数的值后移一位(d=c,c=b,b=undefined),这样设计确保 c 为数组类型或原始数据类型。然后再判断若为用户手写 render() 函数则将 d 赋值为 2,最后调用 _createElement(vm,a,b,c,d) 函数。

4、接着执行 _createElement(vm,tag,data,children,nType) 函数,然后进行规则校验。如下:

  • 如果 data && data.__ob__ 都存在且不为 null || undefined,则直接 return 一个空 VNode 对象。不能用响应式的 data 对象作为 VNode 数据。
  • data && data.is 都存在且不为 null || undefined,则设置 tag = data.is;,而此处 data.is 是组件的属性。若 tag 不存在则直接 return 一个空 VNode 对象。

5、创建 VNode,对 tag 类型做判断, 如下:

  • 若为 String 类型且为原生 Html 标签,则直接创建对象 vnode = new VNode()
  • 若为 String 类型且为已经注册的组件名,则通过 vnode = createComponet() 函数创建组件 VNode 对象。
  • 若为 String 类型且不符合上述两条则直接创建一个未知标签的 VNode 对象。
  • 若为非 String 类型,则直接通过 vnode = createComponet() 函数创建组件 VNode 对象。

又上图 2 可知,创建 vnode 的同时,由于是多次递归调用 createElement() => _createElement() 函数,所以当前生成的 vnode 就会当做参数自动传入 createElement() 函数内作为生成下一个 vnode 的子节点,所以就绑定 children vnode 属性,就这样形成 vnode tree。递归创建 vnode,先创建子 vnode,再创建父 vnode 节点。最后返回 vnode 树.