Skip to content

Commit 4eee49a

Browse files
author
zhangxixi
committed
feat(src): 添加注释
1 parent 6f7dfd3 commit 4eee49a

File tree

5 files changed

+132
-4
lines changed

5 files changed

+132
-4
lines changed

src/core/instance/lifecycle.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function lifecycleMixin (Vue: Class<Component>) {
7777
// restoreActiveInstance是一个函数
7878
const restoreActiveInstance = setActiveInstance(vm)
7979
// 把新的vnode赋给vm,这个vnode就是通过render函数得到的,和$vnode是父子关系
80-
// 所以_vnode和$vnode是一种父子关系
80+
// 所以_vnode和$vnode是一种父子关系,$vnode是父
8181
vm._vnode = vnode
8282
// Vue.prototype.__patch__ is injected in entry points
8383
// based on the rendering backend used.
@@ -236,6 +236,8 @@ export function mountComponent (
236236
return vm
237237
}
238238

239+
// updateChildComponent 的逻辑也非常简单,由于更新了 vnode,那么 vnode 对应的实例 vm 的
240+
// 一系列属性也会发生变化,包括占位符 vm.$vnode 的更新、slot 的更新,listeners 的更新,props 的更新等等。
239241
export function updateChildComponent (
240242
vm: Component,
241243
propsData: ?Object,

src/core/vdom/create-component.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,18 @@ const componentVNodeHooks = {
5454
// 而是在这里执行$mount过程
5555
// 不考虑服务端渲染情况,这里执行的是child.$mount(undefined, false)
5656
// 最终执行的是mountComponent(在instance/lifecycle.js里)
57-
// 进而执行vm._render()(在instance/render.js里),这个过程会设置$vnode,这是父组件的渲染,vnode和$vnode是一种复父子关系
57+
// 进而执行vm._render()(在instance/render.js里),这个过程会设置$vnode,这是父组件的渲染,vnode和$vnode是一种父子关系,$vnode是父
5858
// 最后执行vm._update()(在instance/lifecycle.js里)
5959
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
6060
}
6161
},
6262

63+
// prepatch 方法就是拿到新的 vnode 的组件配置以及组件实例,去执行 updateChildComponent 方法
64+
// updateChildComponent是在instance/lifecycle.js里定义的
6365
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
66+
// 拿到新的 vnode 的组件配置
6467
const options = vnode.componentOptions
68+
// 拿到新的 vnode 的组件实例
6569
const child = vnode.componentInstance = oldVnode.componentInstance
6670
updateChildComponent(
6771
child,

src/core/vdom/patch.js

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ export const emptyNode = new VNode('', {}, [])
3232

3333
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
3434

35+
/*
36+
(key的作用)
37+
如果两个 vnode 的 key 不相等,则是不同的;
38+
否则继续判断对于同步组件,则判断 isComment、data、input 类型等是否相同;
39+
对于异步组件,则判断 asyncFactory 是否相同。
40+
*/
3541
function sameVnode (a, b) {
3642
return (
3743
a.key === b.key && (
@@ -57,6 +63,7 @@ function sameInputType (a, b) {
5763
return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
5864
}
5965

66+
// key的作用
6067
function createKeyToOldIdx (children, beginIdx, endIdx) {
6168
let i, key
6269
const map = {}
@@ -179,6 +186,7 @@ export function createPatchFunction (backend) {
179186

180187
let creatingElmInVPre = 0
181188

189+
// 初次渲染时nested,ownerArray,index都未定义
182190
function createElm (
183191
vnode,
184192
insertedVnodeQueue,
@@ -197,16 +205,19 @@ export function createPatchFunction (backend) {
197205
vnode = ownerArray[index] = cloneVNode(vnode)
198206
}
199207

208+
// 初次渲染时nested未定义,此时isRootInsert就为true
200209
// createChildren里会递归执行createElm,这时候传入的nested为true
201210
vnode.isRootInsert = !nested // for transition enter check
202211
// 如果createComponent为true,就直接返回
212+
// 如果是渲染组件,则返回为true,否则返回为undefined,对于$mount的初次渲染,返回是undefined
203213
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
204214
return
205215
}
206216

207217
const data = vnode.data
208218
const children = vnode.children
209219
const tag = vnode.tag
220+
// 如果定义了tag,说明是一个element
210221
if (isDef(tag)) {
211222
if (process.env.NODE_ENV !== 'production') {
212223
if (data && data.pre) {
@@ -222,9 +233,14 @@ export function createPatchFunction (backend) {
222233
}
223234
}
224235

236+
// 如果有ns,则可能是svg这种,需要调用平台相关的createElementNS方法
237+
// 其他情况则调用平台相关的createElement方法
238+
// 平台相关的createElement方法其实就是对document.createElement方法的封装
239+
// 返回的就是document.createElement的结果
225240
vnode.elm = vnode.ns
226241
? nodeOps.createElementNS(vnode.ns, tag)
227242
: nodeOps.createElement(tag, vnode)
243+
// TODO: 为scoped css设置scopeId,以后再看
228244
setScope(vnode)
229245

230246
/* istanbul ignore if */
@@ -247,20 +263,27 @@ export function createPatchFunction (backend) {
247263
insert(parentElm, vnode.elm, refElm)
248264
}
249265
} else {
266+
// 递归创建子节点
267+
// createChildren 的逻辑很简单,实际上是遍历子虚拟节点,递归调用 createElm,这是一种常用的深度优先的遍历算法,这里要注意的一点是在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入。
250268
createChildren(vnode, children, insertedVnodeQueue)
269+
// 接着再调用 invokeCreateHooks 方法执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue 中。
251270
if (isDef(data)) {
252271
invokeCreateHooks(vnode, insertedVnodeQueue)
253272
}
273+
// 最后调用 insert 方法把 DOM 插入到父节点中,因为是递归调用,子元素会优先调用 insert,所以整个 vnode 树节点的插入顺序是先子后父。
274+
// insert 逻辑很简单,调用一些 nodeOps 把子节点插入到父节点中,这些辅助方法定义在 src/platforms/web/runtime/node-ops.js 中:
254275
insert(parentElm, vnode.elm, refElm)
255276
}
256277

257278
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
258279
creatingElmInVPre--
259280
}
260281
} else if (isTrue(vnode.isComment)) {
282+
// 如果是comment
261283
vnode.elm = nodeOps.createComment(vnode.text)
262284
insert(parentElm, vnode.elm, refElm)
263285
} else {
286+
// 其他情况
264287
vnode.elm = nodeOps.createTextNode(vnode.text)
265288
insert(parentElm, vnode.elm, refElm)
266289
}
@@ -343,6 +366,7 @@ export function createPatchFunction (backend) {
343366
}
344367
}
345368

369+
// createChildren 的逻辑很简单,实际上是遍历子虚拟节点,递归调用 createElm,这是一种常用的深度优先的遍历算法,这里要注意的一点是在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入。
346370
function createChildren (vnode, children, insertedVnodeQueue) {
347371
if (Array.isArray(children)) {
348372
if (process.env.NODE_ENV !== 'production') {
@@ -358,6 +382,7 @@ export function createPatchFunction (backend) {
358382
}
359383

360384
function isPatchable (vnode) {
385+
// 如果是组件实例,则找其子节点,判断其子节点tag是否存在
361386
while (vnode.componentInstance) {
362387
vnode = vnode.componentInstance._vnode
363388
}
@@ -508,23 +533,33 @@ export function createPatchFunction (backend) {
508533
oldEndVnode = oldCh[--oldEndIdx]
509534
newStartVnode = newCh[++newStartIdx]
510535
} else {
536+
// createKeyToOldIdx返回的是一个map,键是key,值是对应的oldIdx
511537
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
538+
// 如果有key,可以通过key拿到新节点在旧节点列表中对应的index
539+
// 如果没有key,则通过遍历查找index
512540
idxInOld = isDef(newStartVnode.key)
513541
? oldKeyToIdx[newStartVnode.key]
514542
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
515543
if (isUndef(idxInOld)) { // New element
544+
// 如果idxInOld不存在,则说明是新元素
516545
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
517546
} else {
547+
// 如果idxInOld存在,则需要移动旧元素
518548
vnodeToMove = oldCh[idxInOld]
519549
if (sameVnode(vnodeToMove, newStartVnode)) {
550+
// 如果要移动的旧节点与第一个新节点相同,则执行patchVnode,把新节点patch到旧节点上
520551
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
552+
// 把旧节点置为undefined
521553
oldCh[idxInOld] = undefined
554+
// 把要移动的旧元素插入到旧节点的前面
522555
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
523556
} else {
557+
// 如果是相同的key,但不是相同vnode,则当成新元素
524558
// same key but different element. treat as new element
525559
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
526560
}
527561
}
562+
// 将newStartVnode指向下一个新元素
528563
newStartVnode = newCh[++newStartIdx]
529564
}
530565
}
@@ -536,6 +571,7 @@ export function createPatchFunction (backend) {
536571
}
537572
}
538573

574+
// 检查是否有重复的key
539575
function checkDuplicateKeys (children) {
540576
const seenKeys = {}
541577
for (let i = 0; i < children.length; i++) {
@@ -554,13 +590,15 @@ export function createPatchFunction (backend) {
554590
}
555591
}
556592

593+
// 查找node在oldCh中的index
557594
function findIdxInOld (node, oldCh, start, end) {
558595
for (let i = start; i < end; i++) {
559596
const c = oldCh[i]
560597
if (isDef(c) && sameVnode(node, c)) return i
561598
}
562599
}
563600

601+
// patchVnode 的作用就是把新的 vnode patch 到旧的 vnode 上,这里我们只关注关键的核心逻辑,我把它拆成四步骤:
564602
function patchVnode (
565603
oldVnode,
566604
vnode,
@@ -580,6 +618,7 @@ export function createPatchFunction (backend) {
580618

581619
const elm = vnode.elm = oldVnode.elm
582620

621+
// 服务端渲染
583622
if (isTrue(oldVnode.isAsyncPlaceholder)) {
584623
if (isDef(vnode.asyncFactory.resolved)) {
585624
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
@@ -602,20 +641,31 @@ export function createPatchFunction (backend) {
602641
return
603642
}
604643

644+
// 第一步:先去执行prepatch函数,当更新的 vnode 是一个组件 vnode 的时候,会执行 prepatch 的方法,它的定义是在create-componnet.js里
645+
// 只有组件vnode才有prepatch方法
646+
// prepatch函数内部调用了updateChildComponent方法
605647
let i
606648
const data = vnode.data
607649
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
608650
i(oldVnode, vnode)
609651
}
610652

653+
// 在执行完新的 vnode 的 prepatch 钩子函数,会执行所有 module 的 update 钩子函数以及
654+
// 用户自定义 update 钩子函数,对于 module 的钩子函数,之后我们会有具体的章节针对一些具体的 case 分析。
611655
const oldCh = oldVnode.children
612656
const ch = vnode.children
613657
if (isDef(data) && isPatchable(vnode)) {
614658
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
615659
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
616660
}
661+
// 如果 vnode 是个文本节点且新旧文本不相同,则直接替换文本内容。如果不是文本节点,则判断它们的子节点,并分了几种情况处理:
662+
// oldCh 与 ch 都存在且不相同时,使用 updateChildren 函数来更新子节点,这个后面重点讲。
663+
// 2.如果只有 ch 存在,表示旧节点不需要了。如果旧的节点是文本节点则先将节点的文本清除,然后通过 addVnodes 将 ch 批量插入到新节点 elm 下。
664+
// 3.如果只有 oldCh 存在,表示更新的是空节点,则需要将旧的节点通过 removeVnodes 全部清除。
665+
// 4.当只有旧节点是文本节点的时候,则清除其节点文本内容。
617666
if (isUndef(vnode.text)) {
618667
if (isDef(oldCh) && isDef(ch)) {
668+
// updateChildren逻辑复杂
619669
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
620670
} else if (isDef(ch)) {
621671
if (process.env.NODE_ENV !== 'production') {
@@ -626,11 +676,13 @@ export function createPatchFunction (backend) {
626676
} else if (isDef(oldCh)) {
627677
removeVnodes(oldCh, 0, oldCh.length - 1)
628678
} else if (isDef(oldVnode.text)) {
679+
// oldCh、ch、vnode.text不存在,但oldVnode.text存在
629680
nodeOps.setTextContent(elm, '')
630681
}
631682
} else if (oldVnode.text !== vnode.text) {
632683
nodeOps.setTextContent(elm, vnode.text)
633684
}
685+
// 执行 postpatch 钩子函数,它是组件自定义的钩子函数,有则执行
634686
if (isDef(data)) {
635687
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
636688
}
@@ -775,21 +827,27 @@ export function createPatchFunction (backend) {
775827
isInitialPatch = true
776828
createElm(vnode, insertedVnodeQueue)
777829
} else {
830+
// 如果nodeType存在,则说明是一个真实的element。
831+
// 当初次渲染时,执行$mount(el),此时el就是oldVnode,其nodeType是存在的
778832
const isRealElement = isDef(oldVnode.nodeType)
779833
if (!isRealElement && sameVnode(oldVnode, vnode)) {
780834
// 新旧节点相同
781835
// patch existing root node
782836
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
783837
} else {
784-
// 新旧节点不同
838+
// 如果是初次渲染,isRealElement是true
839+
// 如果是更新,则isRealElement是false
785840
if (isRealElement) {
786841
// mounting to a real element
787842
// check if this is server-rendered content and if we can perform
788843
// a successful hydration.
844+
// nodeType=1表示一个元素节点
845+
// 如果是元素节点且有属性SSR_ATTR,则设置hydrating为true
789846
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
790847
oldVnode.removeAttribute(SSR_ATTR)
791848
hydrating = true
792849
}
850+
// 如果是服务端渲染,则执行服务端渲染的逻辑
793851
if (isTrue(hydrating)) {
794852
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
795853
invokeInsertHook(vnode, insertedVnodeQueue, true)
@@ -806,12 +864,16 @@ export function createPatchFunction (backend) {
806864
}
807865
// either not server-rendered, or hydration failed.
808866
// create an empty node and replace it
867+
// 如果不是服务端渲染,或者hydration失败,则通过emptyNodeAt方法把oldVnode转成Vnode对象,并替换掉oldVnode
809868
oldVnode = emptyNodeAt(oldVnode)
810869
}
811870

812-
// 第一步:以旧节点为参考节点,创建新节点,并插入到旧节点的父元素中
871+
// 新旧节点不同,则属于更新过程。大体分为三步:
813872
// replacing existing element
873+
// 第一步:以旧节点为参考节点,创建新节点,并插入到旧节点的父元素中
874+
// 初次渲染时oldVnode.elm就是$mount挂载的元素
814875
const oldElm = oldVnode.elm
876+
// 获取oldElm对应的父节点
815877
const parentElm = nodeOps.parentNode(oldElm)
816878

817879
// create new node
@@ -827,15 +889,23 @@ export function createPatchFunction (backend) {
827889

828890
// 第二步:更新父的占位符
829891
// update parent placeholder node element, recursively
892+
// 我们只关注主要逻辑即可,找到当前 vnode 的父的占位符节点,先执行各个 module 的 destroy 的钩子函数
893+
// 如果当前占位符是一个可挂载的节点,则执行 module 的 create 钩子函数
830894
if (isDef(vnode.parent)) {
831895
let ancestor = vnode.parent
896+
// isPatchable函数是根据tag是否存在判断,patchable含义是可挂载
897+
// 如果是普通vnode,如果vnode.tag存在,则返回true
898+
// 如果是组件实例,则递归寻找组件的子组件,直到不是组件实例
832899
const patchable = isPatchable(vnode)
833900
while (ancestor) {
901+
// cbs存放各个module的钩子
902+
// 执行各个module的destroy函数
834903
for (let i = 0; i < cbs.destroy.length; ++i) {
835904
cbs.destroy[i](ancestor)
836905
}
837906
ancestor.elm = vnode.elm
838907
if (patchable) {
908+
// 如果可挂载,则执行module的create函数
839909
for (let i = 0; i < cbs.create.length; ++i) {
840910
cbs.create[i](emptyNode, ancestor)
841911
}
@@ -859,6 +929,13 @@ export function createPatchFunction (backend) {
859929
// 第三步:销毁旧节点
860930
// destroy old node
861931
if (isDef(parentElm)) {
932+
// 如果父节点存在,则执行removeVnodes
933+
// 删除节点逻辑很简单,就是遍历待删除的 vnodes 做删除,其中
934+
// removeAndInvokeRemoveHook 的作用是从 DOM 中移除节点并执行 module 的
935+
// remove 钩子函数,并对它的子节点递归调用 removeAndInvokeRemoveHook 函数;
936+
// invokeDestroyHook 是执行 module 的 destory 钩子函数以及 vnode 的
937+
// destory 钩子函数,并对它的子 vnode 递归调用 invokeDestroyHook 函数;
938+
// removeNode 就是调用平台的 DOM API 去把真正的 DOM 节点移除。
862939
removeVnodes([oldVnode], 0, 0)
863940
} else if (isDef(oldVnode.tag)) {
864941
invokeDestroyHook(oldVnode)

0 commit comments

Comments
 (0)