1
+ <!DOCTYPE html>
2
+ < html lang ="en ">
3
+
4
+ < head >
5
+ < meta charset ="UTF-8 ">
6
+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
7
+ < title > Document</ title >
8
+ </ head >
9
+
10
+ < body >
11
+ < div id ="app "> </ div >
12
+
13
+ < script >
14
+
15
+ function shouldSetAsProps ( el , key , value ) {
16
+ if ( key === 'form' && el . tagName === 'INPUT' ) return false
17
+ return key in el
18
+ }
19
+
20
+ function createRenderer ( options ) {
21
+
22
+ const {
23
+ createElement,
24
+ insert,
25
+ setElementText,
26
+ patchProps,
27
+ createText,
28
+ setText
29
+ } = options
30
+
31
+ function mountElement ( vnode , container , anchor ) {
32
+ const el = vnode . el = createElement ( vnode . type )
33
+ if ( typeof vnode . children === 'string' ) {
34
+ setElementText ( el , vnode . children )
35
+ } else if ( Array . isArray ( vnode . children ) ) {
36
+ vnode . children . forEach ( child => {
37
+ patch ( null , child , el )
38
+ } )
39
+ }
40
+
41
+ if ( vnode . props ) {
42
+ for ( const key in vnode . props ) {
43
+ patchProps ( el , key , null , vnode . props [ key ] )
44
+ }
45
+ }
46
+
47
+ insert ( el , container , anchor )
48
+ }
49
+
50
+ function patchChildren ( n1 , n2 , container ) {
51
+ if ( typeof n2 . children === 'string' ) {
52
+ if ( Array . isArray ( n1 . children ) ) {
53
+ n1 . children . forEach ( ( c ) => unmount ( c ) )
54
+ }
55
+ setElementText ( container , n2 . children )
56
+ } else if ( Array . isArray ( n2 . children ) ) {
57
+ const oldChildren = n1 . children
58
+ const newChildren = n2 . children
59
+
60
+ // 用来存储寻找过程中遇到的最大索引值
61
+ let lastIndex = 0
62
+ // 遍历新的 children
63
+ for ( let i = 0 ; i < newChildren . length ; i ++ ) {
64
+
65
+ const newVNode = newChildren [ i ] ;
66
+ // 遍历旧的 children
67
+ // 在第一层循环中定义变量 find,代表是否在旧的一组子节点中找到可复用的节点,
68
+ // 初始值为 false,代表没找到
69
+ let find = false
70
+ for ( let j = 0 ; j < oldChildren . length ; j ++ ) {
71
+ const oldVNode = oldChildren [ j ]
72
+ // 如果找到了具有相同 key 值的两个节点,说明可以复用但仍然需要调用 patch 函数更新
73
+ if ( newVNode . key === oldVNode . key ) {
74
+ // 一旦找到可复用的节点,则将变量 find 的值设为 true
75
+ find = true
76
+ patch ( oldVNode , newVNode , container )
77
+ if ( j < lastIndex ) {
78
+ // 如果当前找到的旧 children 中 的索引小于最大索引值 lastIndex,
79
+ // 说明改节点对应的真实 DOM 需要 移动
80
+ const prevVNode = newChildren [ i - 1 ]
81
+ // 如果 prevVNode 不存在,则说明当前 newVNode 是第一个节点,它不需要移动
82
+ if ( prevVNode ) {
83
+ // 由于我们要将 newVNode 对应的真实 DOM 移动到 prevVNode 所对应真实 DOM 后面,
84
+ // 所以我们需要获取 prevVNode 所对应真实 DOM 的下一个兄弟节点,并将其作为锚点
85
+ const anchor = prevVNode . el . nextSibling
86
+ // 调用 insert 方法将 newVNode 对应的真实 DOM 插入到锚点元素前面,
87
+ // 也就是 prevVNode 对应真实 DOM 的后面
88
+ insert ( newVNode . el , container , anchor )
89
+ }
90
+ } else {
91
+ // 如果当前找到的节点在旧 children 中的索引不小于最大索引值,
92
+ // 则更新 lastIndex 的值
93
+ lastIndex == j
94
+ }
95
+ break // 这里需要 break
96
+ }
97
+ }
98
+ // 如果代码运行到这里,find 仍然为 false,
99
+ // 说明当前 newVNode 没有在旧的一组子节点中找到可复用的节点
100
+ // 也就是说,当前 newVNode 是新增节点,需要挂载
101
+ if ( ! find ) {
102
+ // 为了将节点挂载到正确位置,我们需要先获取锚点元素
103
+ // 首先获取当前 newVNode 的前一个 vnode 节点
104
+ const prevVNode = newChildren [ i - 1 ]
105
+ let anchor = null
106
+ if ( prevVNode ) {
107
+ // 如果有前一个 vnode 节点,则使用它的下一个兄弟节点作为锚点元素
108
+ anchor = prevVNode . el . nextSibling
109
+ } else {
110
+ // 如果没有前一个 vnode 节点,说明即将挂载的新节点是第一个子节点
111
+ // 这时我们使用容器元素的 firstChild 作为锚点
112
+ anchor = container . firstChild
113
+ }
114
+ // 挂载 newVNode
115
+ patch ( null , newVNode , container , anchor )
116
+ }
117
+ }
118
+ // 上一步的更新操作完成后
119
+ // 遍历旧的一组子节点
120
+ for ( let i = 0 ; i < oldChildren . length ; i ++ ) {
121
+ const oldVNode = oldChildren [ i ]
122
+ // 拿旧子节点 oldVNode 去新的一组子节点中寻找具有相同 key 值的节点
123
+ const has = newChildren . find (
124
+ vnode => vnode . key === oldVNode . key
125
+ )
126
+ if ( ! has ) {
127
+ // 如果没有找到具有相同 key 值的节点,则说明需要删除该节点
128
+ // 调用 unmount 函数将其卸载
129
+ unmount ( oldVNode )
130
+ }
131
+ }
132
+ } else {
133
+ if ( Array . isArray ( n1 . children ) ) {
134
+ n1 . children . forEach ( c => unmount ( c ) )
135
+ } else if ( typeof n1 . children === 'string' ) {
136
+ setElementText ( container , '' )
137
+ }
138
+ }
139
+ }
140
+
141
+ function patchElement ( n1 , n2 ) {
142
+ // 新的 vnode 也引用了真实 DOM 元素
143
+ const el = n2 . el = n1 . el
144
+ const oldProps = n1 . props
145
+ const newProps = n2 . props
146
+
147
+ for ( const key in newProps ) {
148
+ if ( newProps [ key ] !== oldProps [ key ] ) {
149
+ patchProps ( el , key , oldProps [ key ] , newProps [ key ] )
150
+ }
151
+ }
152
+ for ( const key in oldProps ) {
153
+ if ( ! ( key in newProps ) ) {
154
+ patchProps ( el , key , oldProps [ key ] , null )
155
+ }
156
+ }
157
+
158
+ patchChildren ( n1 , n2 , el )
159
+ }
160
+
161
+ function unmount ( vnode ) {
162
+ if ( vnode . type === Fragment ) {
163
+ vnode . children . forEach ( c => unmount ( c ) )
164
+ return
165
+ }
166
+ const parent = vnode . el . parentNode
167
+ if ( parent ) {
168
+ parent . removeChild ( vnode . el )
169
+ }
170
+ }
171
+
172
+ function patch ( n1 , n2 , container , anchor ) {
173
+ if ( n1 && n1 . type !== n2 . type ) {
174
+ unmount ( n1 )
175
+ n1 = null
176
+ }
177
+
178
+ const { type } = n2
179
+
180
+ if ( typeof type === 'string' ) {
181
+ if ( ! n1 ) {
182
+ mountElement ( n2 , container , anchor )
183
+ } else {
184
+ patchElement ( n1 , n2 )
185
+ }
186
+ } else if ( type === Text ) {
187
+ if ( ! n1 ) {
188
+ const el = n2 . el = createText ( n2 . children )
189
+ insert ( el , container )
190
+ } else {
191
+ const el = n2 . el = n1 . el
192
+ if ( n2 . children !== n1 . children ) {
193
+ setText ( el , n2 . children )
194
+ }
195
+ }
196
+ } else if ( type === Fragment ) {
197
+ if ( ! n1 ) {
198
+ n2 . children . forEach ( c => patch ( null , c , container ) )
199
+ } else {
200
+ patchChildren ( n1 , n2 , container )
201
+ }
202
+ }
203
+ }
204
+
205
+ function render ( vnode , container ) {
206
+ if ( vnode ) {
207
+ // 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁
208
+ patch ( container . _vnode , vnode , container )
209
+ } else {
210
+ if ( container . _vnode ) {
211
+ // 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作
212
+ unmount ( container . _vnode )
213
+ }
214
+ }
215
+ // 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode
216
+ container . _vnode = vnode
217
+ }
218
+
219
+ return {
220
+ render
221
+ }
222
+ }
223
+
224
+ const renderer = createRenderer ( {
225
+ createElement ( tag ) {
226
+ return document . createElement ( tag )
227
+ } ,
228
+ setElementText ( el , text ) {
229
+ el . textContent = text
230
+ } ,
231
+ insert ( el , parent , anchor = null ) {
232
+ // insertBefore 需要锚点元素 anchor
233
+ parent . insertBefore ( el , anchor )
234
+ } ,
235
+ createText ( text ) {
236
+ return document . createTextNode ( text )
237
+ } ,
238
+ setText ( el , text ) {
239
+ el . nodeValue = text
240
+ } ,
241
+ patchProps ( el , key , prevValue , nextValue ) {
242
+ if ( / ^ o n / . test ( key ) ) {
243
+ const invokers = el . _vei || ( el . _vei = { } )
244
+ let invoker = invokers [ key ]
245
+ const name = key . slice ( 2 ) . toLowerCase ( )
246
+ if ( nextValue ) {
247
+ if ( ! invoker ) {
248
+ invoker = el . _vei [ key ] = ( e ) => {
249
+ console . log ( e . timeStamp )
250
+ console . log ( invoker . attached )
251
+ if ( e . timeStamp < invoker . attached ) return
252
+ if ( Array . isArray ( invoker . value ) ) {
253
+ invoker . value . forEach ( fn => fn ( e ) )
254
+ } else {
255
+ invoker . value ( e )
256
+ }
257
+ }
258
+ invoker . value = nextValue
259
+ invoker . attached = performance . now ( )
260
+ el . addEventListener ( name , invoker )
261
+ } else {
262
+ invoker . value = nextValue
263
+ }
264
+ } else if ( invoker ) {
265
+ el . removeEventListener ( name , invoker )
266
+ }
267
+ } else if ( key === 'class' ) {
268
+ el . className = nextValue || ''
269
+ } else if ( shouldSetAsProps ( el , key , nextValue ) ) {
270
+ const type = typeof el [ key ]
271
+ if ( type === 'boolean' && nextValue === '' ) {
272
+ el [ key ] = true
273
+ } else {
274
+ el [ key ] = nextValue
275
+ }
276
+ } else {
277
+ el . setAttribute ( key , nextValue )
278
+ }
279
+ }
280
+ } )
281
+
282
+ const VNode1 = {
283
+ type : 'div' ,
284
+ children : [
285
+ { type : 'p' , children : '1' , key : 1 } ,
286
+ { type : 'p' , children : '2' , key : 2 } ,
287
+ { type : 'p' , children : 'hello' , key : 3 }
288
+ ]
289
+ }
290
+ renderer . render ( VNode1 , document . querySelector ( '#app' ) )
291
+
292
+ const VNode2 = {
293
+ type : 'div' ,
294
+ children : [
295
+ { type : 'p' , children : '4' , key : 4 } ,
296
+ { type : 'p' , children : 'world' , key : 3 } ,
297
+ { type : 'p' , children : '1' , key : 1 } ,
298
+ { type : 'p' , children : '2' , key : 2 }
299
+ ]
300
+ }
301
+
302
+ setTimeout ( ( ) => {
303
+ console . log ( 'update' )
304
+ renderer . render ( VNode2 , document . querySelector ( '#app' ) )
305
+ } , 400 ) ;
306
+
307
+
308
+
309
+ </ script >
310
+ </ body >
311
+
312
+ </ html >
0 commit comments