最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

Vue之patch

互联网 admin 2浏览 0评论

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 flowcomponentVNodeHooks.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函数,这部分之后会有对应文章具体细究。

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 flowcomponentVNodeHooks.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)

  1. 暂无评论