We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
上一篇:vue源码阅读三:虚拟 DOM 是如何生成的?(下)
前面用了两篇文章,讲虚拟 DOM 是如何生成的。终于到了如何将虚拟 DOM 渲染成真实 DOM 的部分了。 回顾下之前的mountComponent 函数,中间有行代码如下:
DOM
mountComponent
vm._update(vm._render(), hydrating)
vm._render(),我们已经知道是如何生成虚拟 DOM 的了。接下来,我们看看vm._update是如何将虚拟 DOM 渲染成真实的 DOM的。
vm._render()
vm._update
_update
_update 的主要代码如下:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { ... if (!prevVnode) { // 首次渲染 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // 更新 vm.$el = vm.__patch__(prevVnode, vnode) } ... }
可以看到,_update 主要调用了__patch__ 方法。暂时还没有看过diff算法,就先分析首次的渲染吧。 _update主要是调用了__patch__方法,并将该方法所返回的结果,覆盖之前的vm.$el。
__patch__
diff
vm.$el
先找到__patch__方法在哪里定义的。找到之后,就是下面这样:
Vue.prototype.__patch__ = inBrowser ? patch : noop
这段很好理解,当前环境是在浏览器中时,将patch 赋值给 __patch__。 这时,我们不仅又要问 patch 又是什么呢?
patch
export const patch: Function = createPatchFunction({ nodeOps, modules })
可以看到,patch 就是 createPatchFunction 方法。 所以,_update 实际上相当于是调用了createPatchFunction 方法来生成真实的DOM。需要注意的地方是{ nodeOps, modules }分别是什么。
createPatchFunction
{ nodeOps, modules }
nodeOps:封装了一些原生DOM操作方法。像DOM的创建、插入、移除等都是封装在这里面的。
modules:封装了对原生DOM的特性进行操作的方法。如class/styles 的添加、更新等等。
class/styles
export function createPatchFunction(backend) { ... return function patch(oldVnode, vnode, hydrating, removeOnly) { const isRealElement = isDef(oldVnode.nodeType) // 是真实的 DOM 时,则将真实的 DOM 转为虚拟 DOM if (isRealElement) { oldVnode = emptyNodeAt(oldVnode) } const oldElm = oldVnode.elm // 获取旧节点的父元素 const parentElm = nodeOps.parentNode(oldElm) // 创建真实 DOM createElm( vnode, // 新的虚拟节点 insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, // 要插入的父节点 nodeOps.nextSibling(oldElm) // 下一个节点 ) // 返回真实的 DOM,代替之前的 vm.$el return vnode.elm } }
在第一次的渲染时,旧的虚拟节点oldVnode就是vm.$el,是真实的DOM,会被转为对应的虚拟DOM。然后在获取到oldVnode的父元素之后,将新的虚拟节点vnode转为真实的DOM。
oldVnode
虚拟DOM
vnode
function createElm( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { ... // 创建组件节点 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } const data = vnode.data const children = vnode.children const tag = vnode.tag // 是元素节点 if (isDef(tag)) { // 先创建最外层的元素 vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) // 创建最外层元素内部的节点 createChildren(vnode, children, insertedVnodeQueue) // 将创建好的元素插入到父节点 insert(parentElm, vnode.elm, refElm) } else if (isTrue(vnode.isComment)) { // 注释节点 vnode.elm = nodeOps.createComment(vnode.text) insert(parentElm, vnode.elm, refElm) } else { // 文本节点 vnode.elm = nodeOps.createTextNode(vnode.text) insert(parentElm, vnode.elm, refElm) } ... }
生成真实的DOM主要是这个函数,分为创建组件节点和创建普通节点。
创建普通节点又分为创建元素节点、注释节点和文本节点。
// 创建元素节点 if (isDef(tag)) { // 先创建最外层的元素 vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) // 创建最外层元素内部的节点 createChildren(vnode, children, insertedVnodeQueue) // 将创建好的元素插入到父节点 insert(parentElm, vnode.elm, refElm) } ---------------------------------------------------- // createChildren 方法 function createChildren(vnode, children, insertedVnodeQueue) { // 数组的话,遍历 children,然后递归调用 createElm 方法 if (Array.isArray(children)) { if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(children) } for (let i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i) } } else if (isPrimitive(vnode.text)) { // 文本节点类型, 如 string/number/symbol/boolean,创建并插入到父节点 nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text))) } } ----------------------------------------------------- // insert 方法 function insert(parent, elm, ref) { if (isDef(parent)) { if (isDef(ref)) { if (nodeOps.parentNode(ref) === parent) { nodeOps.insertBefore(parent, elm, ref) } } else { nodeOps.appendChild(parent, elm) } } }
在创建时,先创建最外层的元素,而后创建外层元素内部的元素,如果内部元素是数组组成的虚拟DOM,则递归遍历数组。如果内部元素是文本元素类型(如:string/number/boolean/symbol),则创建文本节点,然后插入到外层元素下面。最后在将外层元素插入到父节点下。
else if (isTrue(vnode.isComment)) { // 注释节点 vnode.elm = nodeOps.createComment(vnode.text) insert(parentElm, vnode.elm, refElm) }
创建注释节点,比较简单。直接创建注释节点,然后插入到父元素下。
else { // 文本节点 vnode.elm = nodeOps.createTextNode(vnode.text) insert(parentElm, vnode.elm, refElm) }
其他情况下,均是创建文本节点,然后插入到父元素下。 整个流程如下:
还有一部分是创建组件节点。这个我们放到下一节将。
真实的DOM
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
前面用了两篇文章,讲虚拟
DOM
是如何生成的。终于到了如何将虚拟DOM
渲染成真实DOM
的部分了。回顾下之前的
mountComponent
函数,中间有行代码如下:vm._render()
,我们已经知道是如何生成虚拟DOM
的了。接下来,我们看看vm._update
是如何将虚拟DOM
渲染成真实的DOM
的。_update
_update
的主要代码如下:可以看到,
_update
主要调用了__patch__
方法。暂时还没有看过diff
算法,就先分析首次的渲染吧。_update
主要是调用了__patch__
方法,并将该方法所返回的结果,覆盖之前的vm.$el
。__patch__
先找到
__patch__
方法在哪里定义的。找到之后,就是下面这样:这段很好理解,当前环境是在浏览器中时,将
patch
赋值给__patch__
。这时,我们不仅又要问
patch
又是什么呢?可以看到,
patch
就是createPatchFunction
方法。所以,
_update
实际上相当于是调用了createPatchFunction
方法来生成真实的DOM
。需要注意的地方是{ nodeOps, modules }
分别是什么。nodeOps:封装了一些原生
DOM
操作方法。像DOM
的创建、插入、移除等都是封装在这里面的。modules:封装了对原生
DOM
的特性进行操作的方法。如class/styles
的添加、更新等等。createPatchFunction
在第一次的渲染时,旧的虚拟节点
oldVnode
就是vm.$el
,是真实的DOM
,会被转为对应的虚拟DOM
。然后在获取到oldVnode
的父元素之后,将新的虚拟节点vnode
转为真实的DOM
。createElm
生成真实的
DOM
主要是这个函数,分为创建组件节点和创建普通节点。创建普通节点
创建普通节点又分为创建元素节点、注释节点和文本节点。
在创建时,先创建最外层的元素,而后创建外层元素内部的元素,如果内部元素是数组组成的虚拟DOM,则递归遍历数组。如果内部元素是文本元素类型(如:string/number/boolean/symbol),则创建文本节点,然后插入到外层元素下面。最后在将外层元素插入到父节点下。
创建注释节点,比较简单。直接创建注释节点,然后插入到父元素下。
其他情况下,均是创建文本节点,然后插入到父元素下。
整个流程如下:
还有一部分是创建组件节点。这个我们放到下一节将。
总结
虚拟DOM
转为真实的DOM
,vm._update
会调用__patch__
方法,而patch
方法实际上则是封装了createPatchFunction
方法。createPatchFunction
方法中,先获取旧节点的父元素,然后将虚拟DOM转为真实DOM,插入到旧节点的父元素下。The text was updated successfully, but these errors were encountered: