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
+ patchKeyedChildren ( n1 , n2 , container )
58
+ } else {
59
+ if ( Array . isArray ( n1 . children ) ) {
60
+ n1 . children . forEach ( c => unmount ( c ) )
61
+ } else if ( typeof n1 . children === 'string' ) {
62
+ setElementText ( container , '' )
63
+ }
64
+ }
65
+ }
66
+
67
+ function patchKeyedChildren ( n1 , n2 , container ) {
68
+ const oldChildren = n1 . children
69
+ const newChildren = n2 . children
70
+
71
+ let oldStartIdx = 0
72
+ let oldEndIdx = oldChildren . length - 1
73
+ let newStartIdx = 0
74
+ let newEndIdx = newChildren . length - 1
75
+
76
+ let oldStartVNode = oldChildren [ oldStartIdx ]
77
+ let oldEndVNode = oldChildren [ oldEndIdx ]
78
+ let newStartVNode = newChildren [ newStartIdx ]
79
+ let newEndVNode = newChildren [ newEndIdx ]
80
+
81
+ while ( oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx ) {
82
+ if ( ! oldStartVNode ) {
83
+ oldStartVNode = oldChildren [ ++ oldStartIdx ]
84
+ } else if ( ! oldEndVNode ) {
85
+ oldEndVNode = oldChildren [ -- oldEndIdx ]
86
+ } else if ( oldStartVNode . key === newStartVNode . key ) {
87
+ patch ( oldStartVNode , newStartVNode , container )
88
+ oldStartVNode = oldChildren [ ++ oldStartIdx ]
89
+ newStartVNode = newChildren [ ++ newStartIdx ]
90
+ } else if ( oldEndVNode . key === newEndVNode . key ) {
91
+ patch ( oldEndVNode , newEndVNode , container )
92
+ oldEndVNode = oldChildren [ -- oldEndIdx ]
93
+ newEndVNode = newChildren [ -- newEndIdx ]
94
+ } else if ( oldStartVNode . key === newEndVNode . key ) {
95
+ patch ( oldStartVNode , newEndVNode , container )
96
+ insert ( oldStartVNode . el , container , newEndVNode . el . nextSibling )
97
+
98
+ oldStartVNode = oldChildren [ ++ oldStartIdx ]
99
+ newEndVNode = newChildren [ -- newEndIdx ]
100
+ } else if ( oldEndVNode . key === newStartVNode . key ) {
101
+ patch ( oldEndVNode , newStartVNode , container )
102
+ insert ( oldEndVNode . el , container , oldStartVNode . el )
103
+
104
+ oldEndVNode = oldChildren [ -- oldEndIdx ]
105
+ newStartVNode = newChildren [ ++ newStartIdx ]
106
+ } else {
107
+ const idxInOld = oldChildren . findIndex (
108
+ node => node . key === newStartVNode . key
109
+ )
110
+ if ( idxInOld > 0 ) {
111
+ const vnodeToMove = oldChildren [ idxInOld ]
112
+ patch ( vnodeToMove , newStartVNode , container )
113
+ insert ( vnodeToMove . el , container , oldStartVNode . el )
114
+ oldChildren [ idxInOld ] = undefined
115
+ } else {
116
+ // 将 newStartVNode 作为新节点挂载到头部,使用当前头部节点 oldStartVNode.el 作为锚点
117
+ patch ( null , newStartVNode , container , oldStartVNode . el )
118
+ }
119
+ newStartVNode = newChildren [ ++ newStartIdx ]
120
+
121
+ }
122
+ }
123
+ if ( oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx ) {
124
+ // 如果满足条件,则说明有新的节点遗留,需要挂载它们
125
+ for ( let i = newStartIdx ; i <= newEndIdx ; i ++ ) {
126
+ patch ( null , newChildren [ i ] , container , oldStartVNode . el )
127
+ }
128
+ } else if ( newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx ) {
129
+ for ( let i = oldStartIdx ; i <= oldEndIdx ; i ++ ) {
130
+ unmount ( oldChildren [ i ] )
131
+ }
132
+ }
133
+ }
134
+
135
+ function patchElement ( n1 , n2 ) {
136
+ const el = n2 . el = n1 . el
137
+ const oldProps = n1 . props
138
+ const newProps = n2 . props
139
+
140
+ for ( const key in newProps ) {
141
+ if ( newProps [ key ] !== oldProps [ key ] ) {
142
+ patchProps ( el , key , oldProps [ key ] , newProps [ key ] )
143
+ }
144
+ }
145
+ for ( const key in oldProps ) {
146
+ if ( ! ( key in newProps ) ) {
147
+ patchProps ( el , key , oldProps [ key ] , null )
148
+ }
149
+ }
150
+
151
+ patchChildren ( n1 , n2 , el )
152
+ }
153
+
154
+ function unmount ( vnode ) {
155
+ if ( vnode . type === Fragment ) {
156
+ vnode . children . forEach ( c => unmount ( c ) )
157
+ return
158
+ }
159
+ const parent = vnode . el . parentNode
160
+ if ( parent ) {
161
+ parent . removeChild ( vnode . el )
162
+ }
163
+ }
164
+
165
+ function patch ( n1 , n2 , container , anchor ) {
166
+ if ( n1 && n1 . type !== n2 . type ) {
167
+ unmount ( n1 )
168
+ n1 = null
169
+ }
170
+
171
+ const { type } = n2
172
+
173
+ if ( typeof type === 'string' ) {
174
+ if ( ! n1 ) {
175
+ mountElement ( n2 , container , anchor )
176
+ } else {
177
+ patchElement ( n1 , n2 )
178
+ }
179
+ } else if ( type === Text ) {
180
+ if ( ! n1 ) {
181
+ const el = n2 . el = createText ( n2 . children )
182
+ insert ( el , container )
183
+ } else {
184
+ const el = n2 . el = n1 . el
185
+ if ( n2 . children !== n1 . children ) {
186
+ setText ( el , n2 . children )
187
+ }
188
+ }
189
+ } else if ( type === Fragment ) {
190
+ if ( ! n1 ) {
191
+ n2 . children . forEach ( c => patch ( null , c , container ) )
192
+ } else {
193
+ patchChildren ( n1 , n2 , container )
194
+ }
195
+ }
196
+ }
197
+
198
+ function render ( vnode , container ) {
199
+ if ( vnode ) {
200
+ patch ( container . _vnode , vnode , container )
201
+ } else {
202
+ if ( container . _vnode ) {
203
+ unmount ( container . _vnode )
204
+ }
205
+ }
206
+ container . _vnode = vnode
207
+ }
208
+
209
+ return {
210
+ render
211
+ }
212
+ }
213
+
214
+ const renderer = createRenderer ( {
215
+ createElement ( tag ) {
216
+ return document . createElement ( tag )
217
+ } ,
218
+ setElementText ( el , text ) {
219
+ el . textContent = text
220
+ } ,
221
+ insert ( el , parent , anchor = null ) {
222
+ parent . insertBefore ( el , anchor )
223
+ } ,
224
+ createText ( text ) {
225
+ return document . createTextNode ( text )
226
+ } ,
227
+ setText ( el , text ) {
228
+ el . nodeValue = text
229
+ } ,
230
+ patchProps ( el , key , prevValue , nextValue ) {
231
+ if ( / ^ o n / . test ( key ) ) {
232
+ const invokers = el . _vei || ( el . _vei = { } )
233
+ let invoker = invokers [ key ]
234
+ const name = key . slice ( 2 ) . toLowerCase ( )
235
+ if ( nextValue ) {
236
+ if ( ! invoker ) {
237
+ invoker = el . _vei [ key ] = ( e ) => {
238
+ console . log ( e . timeStamp )
239
+ console . log ( invoker . attached )
240
+ if ( e . timeStamp < invoker . attached ) return
241
+ if ( Array . isArray ( invoker . value ) ) {
242
+ invoker . value . forEach ( fn => fn ( e ) )
243
+ } else {
244
+ invoker . value ( e )
245
+ }
246
+ }
247
+ invoker . value = nextValue
248
+ invoker . attached = performance . now ( )
249
+ el . addEventListener ( name , invoker )
250
+ } else {
251
+ invoker . value = nextValue
252
+ }
253
+ } else if ( invoker ) {
254
+ el . removeEventListener ( name , invoker )
255
+ }
256
+ } else if ( key === 'class' ) {
257
+ el . className = nextValue || ''
258
+ } else if ( shouldSetAsProps ( el , key , nextValue ) ) {
259
+ const type = typeof el [ key ]
260
+ if ( type === 'boolean' && nextValue === '' ) {
261
+ el [ key ] = true
262
+ } else {
263
+ el [ key ] = nextValue
264
+ }
265
+ } else {
266
+ el . setAttribute ( key , nextValue )
267
+ }
268
+ }
269
+ } )
270
+
271
+ const Fragment = Symbol ( )
272
+ const VNode1 = {
273
+ type : 'div' ,
274
+ children : [
275
+ { type : 'p' , children : '1' , key : 1 } ,
276
+ { type : 'p' , children : '2' , key : 2 } ,
277
+ { type : 'p' , children : '3' , key : 3 }
278
+ ]
279
+ }
280
+ renderer . render ( VNode1 , document . querySelector ( '#app' ) )
281
+
282
+ const VNode2 = {
283
+ type : 'div' ,
284
+ children : [
285
+ { type : 'p' , children : '1' , key : 1 } ,
286
+ { type : 'p' , children : '3' , key : 3 }
287
+ ]
288
+ }
289
+
290
+ setTimeout ( ( ) => {
291
+ console . log ( 'update' )
292
+ renderer . render ( VNode2 , document . querySelector ( '#app' ) )
293
+ } , 400 ) ;
294
+
295
+
296
+
297
+ </ script >
298
+ </ body >
299
+
300
+ </ html >
0 commit comments