@@ -32,6 +32,12 @@ export const emptyNode = new VNode('', {}, [])
32
32
33
33
const hooks = [ 'create' , 'activate' , 'update' , 'remove' , 'destroy' ]
34
34
35
+ /*
36
+ (key的作用)
37
+ 如果两个 vnode 的 key 不相等,则是不同的;
38
+ 否则继续判断对于同步组件,则判断 isComment、data、input 类型等是否相同;
39
+ 对于异步组件,则判断 asyncFactory 是否相同。
40
+ */
35
41
function sameVnode ( a , b ) {
36
42
return (
37
43
a . key === b . key && (
@@ -57,6 +63,7 @@ function sameInputType (a, b) {
57
63
return typeA === typeB || isTextInputType ( typeA ) && isTextInputType ( typeB )
58
64
}
59
65
66
+ // key的作用
60
67
function createKeyToOldIdx ( children , beginIdx , endIdx ) {
61
68
let i , key
62
69
const map = { }
@@ -179,6 +186,7 @@ export function createPatchFunction (backend) {
179
186
180
187
let creatingElmInVPre = 0
181
188
189
+ // 初次渲染时nested,ownerArray,index都未定义
182
190
function createElm (
183
191
vnode ,
184
192
insertedVnodeQueue ,
@@ -197,16 +205,19 @@ export function createPatchFunction (backend) {
197
205
vnode = ownerArray [ index ] = cloneVNode ( vnode )
198
206
}
199
207
208
+ // 初次渲染时nested未定义,此时isRootInsert就为true
200
209
// createChildren里会递归执行createElm,这时候传入的nested为true
201
210
vnode . isRootInsert = ! nested // for transition enter check
202
211
// 如果createComponent为true,就直接返回
212
+ // 如果是渲染组件,则返回为true,否则返回为undefined,对于$mount的初次渲染,返回是undefined
203
213
if ( createComponent ( vnode , insertedVnodeQueue , parentElm , refElm ) ) {
204
214
return
205
215
}
206
216
207
217
const data = vnode . data
208
218
const children = vnode . children
209
219
const tag = vnode . tag
220
+ // 如果定义了tag,说明是一个element
210
221
if ( isDef ( tag ) ) {
211
222
if ( process . env . NODE_ENV !== 'production' ) {
212
223
if ( data && data . pre ) {
@@ -222,9 +233,14 @@ export function createPatchFunction (backend) {
222
233
}
223
234
}
224
235
236
+ // 如果有ns,则可能是svg这种,需要调用平台相关的createElementNS方法
237
+ // 其他情况则调用平台相关的createElement方法
238
+ // 平台相关的createElement方法其实就是对document.createElement方法的封装
239
+ // 返回的就是document.createElement的结果
225
240
vnode . elm = vnode . ns
226
241
? nodeOps . createElementNS ( vnode . ns , tag )
227
242
: nodeOps . createElement ( tag , vnode )
243
+ // TODO: 为scoped css设置scopeId,以后再看
228
244
setScope ( vnode )
229
245
230
246
/* istanbul ignore if */
@@ -247,20 +263,27 @@ export function createPatchFunction (backend) {
247
263
insert ( parentElm , vnode . elm , refElm )
248
264
}
249
265
} else {
266
+ // 递归创建子节点
267
+ // createChildren 的逻辑很简单,实际上是遍历子虚拟节点,递归调用 createElm,这是一种常用的深度优先的遍历算法,这里要注意的一点是在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入。
250
268
createChildren ( vnode , children , insertedVnodeQueue )
269
+ // 接着再调用 invokeCreateHooks 方法执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue 中。
251
270
if ( isDef ( data ) ) {
252
271
invokeCreateHooks ( vnode , insertedVnodeQueue )
253
272
}
273
+ // 最后调用 insert 方法把 DOM 插入到父节点中,因为是递归调用,子元素会优先调用 insert,所以整个 vnode 树节点的插入顺序是先子后父。
274
+ // insert 逻辑很简单,调用一些 nodeOps 把子节点插入到父节点中,这些辅助方法定义在 src/platforms/web/runtime/node-ops.js 中:
254
275
insert ( parentElm , vnode . elm , refElm )
255
276
}
256
277
257
278
if ( process . env . NODE_ENV !== 'production' && data && data . pre ) {
258
279
creatingElmInVPre --
259
280
}
260
281
} else if ( isTrue ( vnode . isComment ) ) {
282
+ // 如果是comment
261
283
vnode . elm = nodeOps . createComment ( vnode . text )
262
284
insert ( parentElm , vnode . elm , refElm )
263
285
} else {
286
+ // 其他情况
264
287
vnode . elm = nodeOps . createTextNode ( vnode . text )
265
288
insert ( parentElm , vnode . elm , refElm )
266
289
}
@@ -343,6 +366,7 @@ export function createPatchFunction (backend) {
343
366
}
344
367
}
345
368
369
+ // createChildren 的逻辑很简单,实际上是遍历子虚拟节点,递归调用 createElm,这是一种常用的深度优先的遍历算法,这里要注意的一点是在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入。
346
370
function createChildren ( vnode , children , insertedVnodeQueue ) {
347
371
if ( Array . isArray ( children ) ) {
348
372
if ( process . env . NODE_ENV !== 'production' ) {
@@ -358,6 +382,7 @@ export function createPatchFunction (backend) {
358
382
}
359
383
360
384
function isPatchable ( vnode ) {
385
+ // 如果是组件实例,则找其子节点,判断其子节点tag是否存在
361
386
while ( vnode . componentInstance ) {
362
387
vnode = vnode . componentInstance . _vnode
363
388
}
@@ -508,23 +533,33 @@ export function createPatchFunction (backend) {
508
533
oldEndVnode = oldCh [ -- oldEndIdx ]
509
534
newStartVnode = newCh [ ++ newStartIdx ]
510
535
} else {
536
+ // createKeyToOldIdx返回的是一个map,键是key,值是对应的oldIdx
511
537
if ( isUndef ( oldKeyToIdx ) ) oldKeyToIdx = createKeyToOldIdx ( oldCh , oldStartIdx , oldEndIdx )
538
+ // 如果有key,可以通过key拿到新节点在旧节点列表中对应的index
539
+ // 如果没有key,则通过遍历查找index
512
540
idxInOld = isDef ( newStartVnode . key )
513
541
? oldKeyToIdx [ newStartVnode . key ]
514
542
: findIdxInOld ( newStartVnode , oldCh , oldStartIdx , oldEndIdx )
515
543
if ( isUndef ( idxInOld ) ) { // New element
544
+ // 如果idxInOld不存在,则说明是新元素
516
545
createElm ( newStartVnode , insertedVnodeQueue , parentElm , oldStartVnode . elm , false , newCh , newStartIdx )
517
546
} else {
547
+ // 如果idxInOld存在,则需要移动旧元素
518
548
vnodeToMove = oldCh [ idxInOld ]
519
549
if ( sameVnode ( vnodeToMove , newStartVnode ) ) {
550
+ // 如果要移动的旧节点与第一个新节点相同,则执行patchVnode,把新节点patch到旧节点上
520
551
patchVnode ( vnodeToMove , newStartVnode , insertedVnodeQueue , newCh , newStartIdx )
552
+ // 把旧节点置为undefined
521
553
oldCh [ idxInOld ] = undefined
554
+ // 把要移动的旧元素插入到旧节点的前面
522
555
canMove && nodeOps . insertBefore ( parentElm , vnodeToMove . elm , oldStartVnode . elm )
523
556
} else {
557
+ // 如果是相同的key,但不是相同vnode,则当成新元素
524
558
// same key but different element. treat as new element
525
559
createElm ( newStartVnode , insertedVnodeQueue , parentElm , oldStartVnode . elm , false , newCh , newStartIdx )
526
560
}
527
561
}
562
+ // 将newStartVnode指向下一个新元素
528
563
newStartVnode = newCh [ ++ newStartIdx ]
529
564
}
530
565
}
@@ -536,6 +571,7 @@ export function createPatchFunction (backend) {
536
571
}
537
572
}
538
573
574
+ // 检查是否有重复的key
539
575
function checkDuplicateKeys ( children ) {
540
576
const seenKeys = { }
541
577
for ( let i = 0 ; i < children . length ; i ++ ) {
@@ -554,13 +590,15 @@ export function createPatchFunction (backend) {
554
590
}
555
591
}
556
592
593
+ // 查找node在oldCh中的index
557
594
function findIdxInOld ( node , oldCh , start , end ) {
558
595
for ( let i = start ; i < end ; i ++ ) {
559
596
const c = oldCh [ i ]
560
597
if ( isDef ( c ) && sameVnode ( node , c ) ) return i
561
598
}
562
599
}
563
600
601
+ // patchVnode 的作用就是把新的 vnode patch 到旧的 vnode 上,这里我们只关注关键的核心逻辑,我把它拆成四步骤:
564
602
function patchVnode (
565
603
oldVnode ,
566
604
vnode ,
@@ -580,6 +618,7 @@ export function createPatchFunction (backend) {
580
618
581
619
const elm = vnode . elm = oldVnode . elm
582
620
621
+ // 服务端渲染
583
622
if ( isTrue ( oldVnode . isAsyncPlaceholder ) ) {
584
623
if ( isDef ( vnode . asyncFactory . resolved ) ) {
585
624
hydrate ( oldVnode . elm , vnode , insertedVnodeQueue )
@@ -602,20 +641,31 @@ export function createPatchFunction (backend) {
602
641
return
603
642
}
604
643
644
+ // 第一步:先去执行prepatch函数,当更新的 vnode 是一个组件 vnode 的时候,会执行 prepatch 的方法,它的定义是在create-componnet.js里
645
+ // 只有组件vnode才有prepatch方法
646
+ // prepatch函数内部调用了updateChildComponent方法
605
647
let i
606
648
const data = vnode . data
607
649
if ( isDef ( data ) && isDef ( i = data . hook ) && isDef ( i = i . prepatch ) ) {
608
650
i ( oldVnode , vnode )
609
651
}
610
652
653
+ // 在执行完新的 vnode 的 prepatch 钩子函数,会执行所有 module 的 update 钩子函数以及
654
+ // 用户自定义 update 钩子函数,对于 module 的钩子函数,之后我们会有具体的章节针对一些具体的 case 分析。
611
655
const oldCh = oldVnode . children
612
656
const ch = vnode . children
613
657
if ( isDef ( data ) && isPatchable ( vnode ) ) {
614
658
for ( i = 0 ; i < cbs . update . length ; ++ i ) cbs . update [ i ] ( oldVnode , vnode )
615
659
if ( isDef ( i = data . hook ) && isDef ( i = i . update ) ) i ( oldVnode , vnode )
616
660
}
661
+ // 如果 vnode 是个文本节点且新旧文本不相同,则直接替换文本内容。如果不是文本节点,则判断它们的子节点,并分了几种情况处理:
662
+ // oldCh 与 ch 都存在且不相同时,使用 updateChildren 函数来更新子节点,这个后面重点讲。
663
+ // 2.如果只有 ch 存在,表示旧节点不需要了。如果旧的节点是文本节点则先将节点的文本清除,然后通过 addVnodes 将 ch 批量插入到新节点 elm 下。
664
+ // 3.如果只有 oldCh 存在,表示更新的是空节点,则需要将旧的节点通过 removeVnodes 全部清除。
665
+ // 4.当只有旧节点是文本节点的时候,则清除其节点文本内容。
617
666
if ( isUndef ( vnode . text ) ) {
618
667
if ( isDef ( oldCh ) && isDef ( ch ) ) {
668
+ // updateChildren逻辑复杂
619
669
if ( oldCh !== ch ) updateChildren ( elm , oldCh , ch , insertedVnodeQueue , removeOnly )
620
670
} else if ( isDef ( ch ) ) {
621
671
if ( process . env . NODE_ENV !== 'production' ) {
@@ -626,11 +676,13 @@ export function createPatchFunction (backend) {
626
676
} else if ( isDef ( oldCh ) ) {
627
677
removeVnodes ( oldCh , 0 , oldCh . length - 1 )
628
678
} else if ( isDef ( oldVnode . text ) ) {
679
+ // oldCh、ch、vnode.text不存在,但oldVnode.text存在
629
680
nodeOps . setTextContent ( elm , '' )
630
681
}
631
682
} else if ( oldVnode . text !== vnode . text ) {
632
683
nodeOps . setTextContent ( elm , vnode . text )
633
684
}
685
+ // 执行 postpatch 钩子函数,它是组件自定义的钩子函数,有则执行
634
686
if ( isDef ( data ) ) {
635
687
if ( isDef ( i = data . hook ) && isDef ( i = i . postpatch ) ) i ( oldVnode , vnode )
636
688
}
@@ -775,21 +827,27 @@ export function createPatchFunction (backend) {
775
827
isInitialPatch = true
776
828
createElm ( vnode , insertedVnodeQueue )
777
829
} else {
830
+ // 如果nodeType存在,则说明是一个真实的element。
831
+ // 当初次渲染时,执行$mount(el),此时el就是oldVnode,其nodeType是存在的
778
832
const isRealElement = isDef ( oldVnode . nodeType )
779
833
if ( ! isRealElement && sameVnode ( oldVnode , vnode ) ) {
780
834
// 新旧节点相同
781
835
// patch existing root node
782
836
patchVnode ( oldVnode , vnode , insertedVnodeQueue , null , null , removeOnly )
783
837
} else {
784
- // 新旧节点不同
838
+ // 如果是初次渲染,isRealElement是true
839
+ // 如果是更新,则isRealElement是false
785
840
if ( isRealElement ) {
786
841
// mounting to a real element
787
842
// check if this is server-rendered content and if we can perform
788
843
// a successful hydration.
844
+ // nodeType=1表示一个元素节点
845
+ // 如果是元素节点且有属性SSR_ATTR,则设置hydrating为true
789
846
if ( oldVnode . nodeType === 1 && oldVnode . hasAttribute ( SSR_ATTR ) ) {
790
847
oldVnode . removeAttribute ( SSR_ATTR )
791
848
hydrating = true
792
849
}
850
+ // 如果是服务端渲染,则执行服务端渲染的逻辑
793
851
if ( isTrue ( hydrating ) ) {
794
852
if ( hydrate ( oldVnode , vnode , insertedVnodeQueue ) ) {
795
853
invokeInsertHook ( vnode , insertedVnodeQueue , true )
@@ -806,12 +864,16 @@ export function createPatchFunction (backend) {
806
864
}
807
865
// either not server-rendered, or hydration failed.
808
866
// create an empty node and replace it
867
+ // 如果不是服务端渲染,或者hydration失败,则通过emptyNodeAt方法把oldVnode转成Vnode对象,并替换掉oldVnode
809
868
oldVnode = emptyNodeAt ( oldVnode )
810
869
}
811
870
812
- // 第一步:以旧节点为参考节点,创建新节点,并插入到旧节点的父元素中
871
+ // 新旧节点不同,则属于更新过程。大体分为三步:
813
872
// replacing existing element
873
+ // 第一步:以旧节点为参考节点,创建新节点,并插入到旧节点的父元素中
874
+ // 初次渲染时oldVnode.elm就是$mount挂载的元素
814
875
const oldElm = oldVnode . elm
876
+ // 获取oldElm对应的父节点
815
877
const parentElm = nodeOps . parentNode ( oldElm )
816
878
817
879
// create new node
@@ -827,15 +889,23 @@ export function createPatchFunction (backend) {
827
889
828
890
// 第二步:更新父的占位符
829
891
// update parent placeholder node element, recursively
892
+ // 我们只关注主要逻辑即可,找到当前 vnode 的父的占位符节点,先执行各个 module 的 destroy 的钩子函数
893
+ // 如果当前占位符是一个可挂载的节点,则执行 module 的 create 钩子函数
830
894
if ( isDef ( vnode . parent ) ) {
831
895
let ancestor = vnode . parent
896
+ // isPatchable函数是根据tag是否存在判断,patchable含义是可挂载
897
+ // 如果是普通vnode,如果vnode.tag存在,则返回true
898
+ // 如果是组件实例,则递归寻找组件的子组件,直到不是组件实例
832
899
const patchable = isPatchable ( vnode )
833
900
while ( ancestor ) {
901
+ // cbs存放各个module的钩子
902
+ // 执行各个module的destroy函数
834
903
for ( let i = 0 ; i < cbs . destroy . length ; ++ i ) {
835
904
cbs . destroy [ i ] ( ancestor )
836
905
}
837
906
ancestor . elm = vnode . elm
838
907
if ( patchable ) {
908
+ // 如果可挂载,则执行module的create函数
839
909
for ( let i = 0 ; i < cbs . create . length ; ++ i ) {
840
910
cbs . create [ i ] ( emptyNode , ancestor )
841
911
}
@@ -859,6 +929,13 @@ export function createPatchFunction (backend) {
859
929
// 第三步:销毁旧节点
860
930
// destroy old node
861
931
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 节点移除。
862
939
removeVnodes ( [ oldVnode ] , 0 , 0 )
863
940
} else if ( isDef ( oldVnode . tag ) ) {
864
941
invokeDestroyHook ( oldVnode )
0 commit comments