Vue之patch

前言

当render函数创建之后,通过watcher实例触发render函数执行后,实际上这个过程会创建子组件、标签等对应的虚拟节点对象VNode,也可能会触发相关属性从而触发视图更新。最后的操作都会流转到patchVNode的处理逻辑即patch阶段。

patch阶段会采用diff算法最大复用DOM,减少DOM成本。本篇不会细究vue diff算法,而是聊聊patch除了diff算法复用DOM之外的其他处理逻辑。

具体分析

还是已最基本的简单实例为示例,贯穿整篇文章,即:

<div id="app">
	{{ text }}
</div>

VNode创建这篇文章可知:

Vue.prototype._render主要作用是调用构建好的render函数,得到vnode
之后将vnode作为参数传入_update,之后调用_update

而_update实例方法则是这篇文章的核心。

_update实例方法

实际上_update中主要的点如下:

var prevNode = vm._vnode;
vm._vnode = vnode;
// 是否已存在vnode对象,不存在就表示是初始化,存在就表示是替换
if (!prevNode) {
	vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false);
} else {
	vm.$el = vm.__patch__(prevNode, vnode);
}

从上面主要代码可知,主要区分是否是初始化调用,根据是否是初始化则__patch__的参数会有所不同。

__patch__实例方法

在这里插入图片描述

__patch__方法的主要逻辑处理如上图,从上图逻辑中可知:

如果已存在vnode,那么实际上会调用patchVnode函数来比较之间的区别
如果不存在vnode,实际上会使用挂载点的DOM对象和vnode来进行处理

首先emptyNode函数的调用。

emptyNode函数

该函数就是创建一个空vnode,即:

new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm);

elm就是当前挂载点的浏览器DOM节点
空vnode对象实际就设置标签名称和elm两个属性

生成vnode之后,接下来的需要关注的处理逻辑:

var oldElm = oldVnode.elm;
// 获取挂载点的父节点,本文示例中就是body节点
var parentElm = nodeOps.parentNode(oldElm);
createElm(vnode, [], parentElm, nodeOps.nextSibling(oldElm));
createElm函数

在这里插入图片描述
上图的主要逻辑可知其主要的处理逻辑:

  • 调用createElement创建vue实例
  • 调用createChildren处理子节点
  • 调用createTextNode处理文本节点
  • 调用insert将节点添加到指定位置

createElement是创建vue实例,子组件在render函数执行时仅仅是创建虚拟节点对象VNode,而子组件对应的Vue实例在patch阶段的createElement被创建。代码逻辑如下:

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    var i = vnode.data;
    if (isDef(i)) {
      	if (isDef(i = i.hook) && isDef(i = i.init)) {
        	i(vnode, false /* hydrating */, parentElm, refElm);
      	}
    }
 }

实际上这里的逻辑是调用虚拟节点VNode的init钩子函数。每一个自定义组件的虚拟节点VNode都对应一组钩子函数,init负责初始化创建Vue实例。init钩子函数具体逻辑如下:

  init: function init (
    vnode,
    hydrating,
    parentElm,
    refElm
  ) {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // keep-alive组件的init处理
      var mountedNode = vnode; // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode);
    } else {
      var child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance,
        parentElm,
        refElm
      );
      child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    }
  },

从上面可知调用createComponentInstanceForVnode实际上就是调用构造函数生成vue实例,而$mount就是执行挂载阶段,而子组件不会有挂载点所以还是会与根对象存在不同的处理逻辑。

insert函数实际上内部是调用insertBefore或appendChild来实现节点插入。

这里额外补充一个逻辑,代码如下:

if (isDef(data)) {
	invokeCreateHooks(vnode, insertedVnodeQueue);
}

执行名create的相关hook,实际上这里涉及到Vue的数据对象等。实际上源码中存在create hook的对象有:

  • directives
  • attrs
  • klass
  • events
  • domProps
  • style
  • transition
  • ref

实际上这些对象的create hook都是做更新操作的,即patch阶段对需要下次显示的DOM执行更新操作,更新对应的class、style、事件等。

setScope函数

主要的功能就是调用setStyleScope函数来给节点添加scopeId。

node.setAttribute(scopeId, '');
removeVnodes函数

顾名思义,该函数是用于实体移除DOM的,结合示例分析梳理主要的处理逻辑如下:
在这里插入图片描述

从上图中可知对于标签节点需要特殊处理,实际上也是处理Vue相关的指令以及remove、destory生命周期相关的处理。

这里主要关注的是removeNode函数,具体的处理:

var parent = nodeOps.parentNode(el);
if (isDef(parent)) nodeOps.removeChild(parent. el);

判断挂载点父节点是否存在,存在就调用removeChild函数来移除指定节点。

createChildren函数

实际上该函数还是调用createElm函数来构建替换节点,主要的处理代码如下:

    if (Array.isArray(children)) {
    // 节点
     for (var i = 0; i < children.length; ++i) {
        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
      }
    } else if (isPrimitive(vnode.text)) {
    // 文本
      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
    }

总结

patch阶段的操作逻辑是非常负责,除了复用DOM,还需要更新相关属性、事件等其他操作,主要的处理梳理如下:

_update -> patch -> createElm + removeNode

createElm内部主要处理逻辑:

createElement -> createChildren -> createComponent创建子组件对应Vue实例 -> createTextNode -> insert

createChildren的处理实际上还是调用createElm函数,实际上就是递归处理。而比较vnode之间的差别主要是patchVnode函数,这部分之后会有对应文章具体细究。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值