├── dom.js
├── index.html
└── vdom.js
/dom.js:
--------------------------------------------------------------------------------
1 |
2 | let div = document.createElement('div')
3 | let str = ''
4 | for (let key in div){
5 | str += key + " "
6 | }
7 | console.log(str)
8 |
9 |
10 |
11 | {# class App extends Component{
12 | render(){
13 | return createElement('p', {id:'article'}, ['文章'])
14 | }
15 | }
16 |
17 | function App1 (){
18 | return createElement('a', {href:'http://www.baidu.com'}, ['百度'])
19 | }
20 | let dom = createElement('div',null, [
21 | createElement(App),
22 | createElement(App1),
23 | createElement('ul', {id:'app'}, [
24 | createElement('li', {class:'item'}, ['列表 1']),
25 | createElement('li', {class:'item'}, ['列表 2']),
26 | createElement('li', {class:'item'}, ['列表 3']),
27 | ])
28 | ])
29 | console.log(dom) #}
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
47 |
48 |
--------------------------------------------------------------------------------
/vdom.js:
--------------------------------------------------------------------------------
1 |
2 | const VNodeType = {
3 | // 组件待扩展
4 | HTML:'HTML',
5 | TEXT:'TEXT',
6 | }
7 | let ChildTyps = {
8 | EMPTY:'EMPTY',
9 | SINGLE:'SINGLE',
10 | MULTIPLE:'MULTIPLE'
11 | }
12 |
13 | function createElement(tag, data = null, children = null) {
14 | // 确定 flags
15 | let flags
16 | if (typeof tag === 'string') {
17 | flags = VNodeType.HTML
18 | } else if(typeof tag === 'function'){
19 | // 组件 未完待续
20 | flags = VNodeType.COMPONENT
21 | } else{
22 | flags = VNodeType.TEXT
23 | }
24 | // 确定 childFlags
25 | let childFlags = null
26 | if (Array.isArray(children)) {
27 | const { length } = children
28 | if (length === 0) {
29 | // 没有 children
30 | childFlags = ChildTyps.EMPTY
31 | } else {
32 | // 多个子节点,且子节点使用key
33 | childFlags = ChildTyps.MULTIPLE
34 | }
35 | } else if (children == null) {
36 | // 没有子节点
37 | childFlags = ChildTyps.EMPTY
38 | } else {
39 | // 其他情况都作为文本节点处理,即单个子节点,会调用 createTextVNode 创建纯文本类型的 VNode
40 | childFlags = ChildTyps.SINGLE
41 | children = createTextVNode(children + '')
42 | }
43 |
44 | // 返回 VNode 对象
45 | return {
46 | flags,
47 | tag,
48 | data,
49 | key: data && data.key,
50 | children,
51 | childFlags,
52 | el: null
53 | }
54 | }
55 |
56 | function createTextVNode(text) {
57 | return {
58 | // flags 是 VNodeType.TEXT
59 | flags: VNodeType.TEXT,
60 | tag: null,
61 | data: null,
62 | // 纯文本类型的 VNode,其 children 属性存储的是与之相符的文本内容
63 | children: text,
64 | // 文本节点没有子节点
65 | childFlags: ChildTyps.EMPTY
66 | }
67 | }
68 | // 更新data
69 | function patchData(el, key, prevValue, nextValue) {
70 | switch (key) {
71 | case 'style':
72 | for (let k in nextValue) {
73 | el.style[k] = nextValue[k]
74 | }
75 | for (let k in prevValue) {
76 | if (!nextValue.hasOwnProperty(k)) {
77 | el.style[k] = ''
78 | }
79 | }
80 | break
81 | case 'class':
82 | el.className = nextValue
83 | break
84 | default:
85 | if (key[0] === '@') {
86 | // 事件
87 | // 移除旧事件
88 | if (prevValue) {
89 | el.removeEventListener(key.slice(1), prevValue)
90 | }
91 | // 添加新事件
92 | if (nextValue) {
93 | el.addEventListener(key.slice(1), nextValue)
94 | }
95 | } else {
96 | // 当做 Attr 处理
97 | el.setAttribute(key, nextValue)
98 | }
99 | break
100 | }
101 | }
102 |
103 |
104 | function render(vnode, container) {
105 | const prevVNode = container.vnode
106 | if (prevVNode == null) {
107 | // 没有旧的 VNode,使用 `mount` 函数挂载全新的 VNode
108 | mount(vnode, container)
109 | // 将新的 VNode 添加到 container.vnode 属性下,这样下一次渲染时旧的 VNode 就存在了
110 | } else {
111 | // 有旧的 VNode,则调用 `patch` 函数打补丁
112 | patch(prevVNode, vnode, container)
113 | // 更新 container.vnode
114 | }
115 | container.vnode = vnode
116 |
117 | }
118 |
119 | function mount(vnode, container, refNode) {
120 | const { flags } = vnode
121 | if (flags == VNodeType.HTML) {
122 | // 挂载普通标签
123 | mountElement(vnode, container, refNode)
124 | }else if (flags == VNodeType.TEXT) {
125 | // 挂载纯文本
126 | mountText(vnode, container)
127 | }
128 | }
129 |
130 | function mountElement(vnode, container, refNode) {
131 | const el = document.createElement(vnode.tag)
132 | vnode.el = el
133 | const data = vnode.data
134 | if (data) {
135 | for (let key in data) {
136 | patchData(el, key, null, data[key])
137 | }
138 | }
139 |
140 | const childFlags = vnode.childFlags
141 | const children = vnode.children
142 | if (childFlags !== ChildTyps.EMPTY) {
143 | if (childFlags == ChildTyps.SINGLE) {
144 | mount(children, el)
145 | } else if (childFlags == ChildTyps.MULTIPLE) {
146 | for (let i = 0; i < children.length; i++) {
147 | mount(children[i], el)
148 | }
149 | }
150 | }
151 | refNode ? container.insertBefore(el, refNode) : container.appendChild(el)
152 | }
153 |
154 | function mountText(vnode, container) {
155 | const el = document.createTextNode(vnode.children)
156 | vnode.el = el
157 | container.appendChild(el)
158 | }
159 |
160 |
161 |
162 | function patch(prevVNode, nextVNode, container) {
163 | const nextFlags = nextVNode.flags
164 | const prevFlags = prevVNode.flags
165 |
166 | if (prevFlags !== nextFlags) {
167 | // 直接替换
168 | replaceVNode(prevVNode, nextVNode, container)
169 | } else if (nextFlags == VNodeType.HTML) {
170 | patchElement(prevVNode, nextVNode, container)
171 | } else if (nextFlags == VNodeType.TEXT) {
172 | patchText(prevVNode, nextVNode)
173 | }
174 | }
175 |
176 | function replaceVNode(prevVNode, nextVNode, container) {
177 | container.removeChild(prevVNode.el)
178 | mount(nextVNode, container)
179 | }
180 |
181 | function patchElement(prevVNode, nextVNode, container) {
182 | // 如果新旧 VNode 描述的是不同的标签,则调用 replaceVNode 函数使用新的 VNode 替换旧的 VNode
183 | if (prevVNode.tag !== nextVNode.tag) {
184 | replaceVNode(prevVNode, nextVNode, container)
185 | return
186 | }
187 |
188 | // 拿到 el 元素,注意这时要让 nextVNode.el 也引用该元素
189 | const el = (nextVNode.el = prevVNode.el)
190 | const prevData = prevVNode.data
191 | const nextData = nextVNode.data
192 |
193 | if (nextData) {
194 | for (let key in nextData) {
195 | const prevValue = prevData[key]
196 | const nextValue = nextData[key]
197 | patchData(el, key, prevValue, nextValue)
198 | }
199 | }
200 | // 删除
201 | if (prevData) {
202 | for (let key in prevData) {
203 | const prevValue = prevData[key]
204 | if (prevValue && !nextData.hasOwnProperty(key)) {
205 | patchData(el, key, prevValue, null)
206 | }
207 | }
208 | }
209 |
210 | // 调用 patchChildren 函数递归的更新子节点
211 | patchChildren(
212 | prevVNode.childFlags, // 旧的 VNode 子节点的类型
213 | nextVNode.childFlags, // 新的 VNode 子节点的类型
214 | prevVNode.children, // 旧的 VNode 子节点
215 | nextVNode.children, // 新的 VNode 子节点
216 | el // 当前标签元素,即这些子节点的父节点
217 | )
218 | }
219 |
220 | function patchChildren(
221 | prevChildFlags,
222 | nextChildFlags,
223 | prevChildren,
224 | nextChildren,
225 | container
226 | ) {
227 | switch (prevChildFlags) {
228 | // 旧的 children 是单个子节点,会执行该 case 语句块
229 | case ChildTyps.SINGLE:
230 | switch (nextChildFlags) {
231 | case ChildTyps.SINGLE:
232 | // 新的 children 也是单个子节点时,会执行该 case 语句块
233 | patch(prevChildren, nextChildren, container)
234 | break
235 | case ChildTyps.EMPTY:
236 | // 新的 children 中没有子节点时,会执行该 case 语句块
237 | container.removeChild(prevChildren.el)
238 | break
239 | default:
240 | // 但新的 children 中有多个子节点时,会执行该 case 语句块
241 | container.removeChild(prevChildren.el)
242 | for (let i = 0; i < nextChildren.length; i++) {
243 | mount(nextChildren[i], container)
244 | }
245 | break
246 | }
247 | break
248 | // 旧的 children 中没有子节点时,会执行该 case 语句块
249 | case ChildTyps.EMPTY:
250 | switch (nextChildFlags) {
251 | case ChildTyps.SINGLE:
252 | // 新的 children 是单个子节点时,会执行该 case 语句块
253 | mount(nextChildren, container)
254 | break
255 | case ChildTyps.EMPTY:
256 | // 新的 children 中没有子节点时,会执行该 case 语句块
257 | break
258 | default:
259 | // 但新的 children 中有多个子节点时,会执行该 case 语句块
260 | for (let i = 0; i < nextChildren.length; i++) {
261 | mount(nextChildren[i], container)
262 | }
263 | break
264 | }
265 | break
266 | // 旧的 children 中有多个子节点时,会执行该 case 语句块
267 | default:
268 | switch (nextChildFlags) {
269 | case ChildTyps.SINGLE:
270 | for (let i = 0; i < prevChildren.length; i++) {
271 | container.removeChild(prevChildren[i].el)
272 | }
273 | mount(nextChildren, container)
274 | break
275 | case ChildTyps.EMPTY:
276 | for (let i = 0; i < prevChildren.length; i++) {
277 | container.removeChild(prevChildren[i].el)
278 | }
279 | break
280 | default:
281 | // 但新的 children 中有多个子节点时,会执行该 case 语句块
282 | let lastIndex = 0
283 | for (let i = 0; i < nextChildren.length; i++) {
284 | const nextVNode = nextChildren[i]
285 | let j = 0,
286 | find = false
287 | for (j; j < prevChildren.length; j++) {
288 | const prevVNode = prevChildren[j]
289 | if (nextVNode.key === prevVNode.key) {
290 | find = true
291 | patch(prevVNode, nextVNode, container)
292 | if (j < lastIndex) {
293 | // 需要移动
294 | const refNode = nextChildren[i - 1].el.nextSibling
295 | container.insertBefore(prevVNode.el, refNode)
296 | break
297 | } else {
298 | // 更新 lastIndex
299 | lastIndex = j
300 | }
301 | }
302 | }
303 | if (!find) {
304 | // 挂载新节点
305 | const refNode =
306 | i - 1 < 0
307 | ? prevChildren[0].el
308 | : nextChildren[i - 1].el.nextSibling
309 |
310 | mount(nextVNode, container, refNode)
311 | }
312 | }
313 | // 移除已经不存在的节点
314 | for (let i = 0; i < prevChildren.length; i++) {
315 | const prevVNode = prevChildren[i]
316 | const has = nextChildren.find(
317 | nextVNode => nextVNode.key === prevVNode.key
318 | )
319 | if (!has) {
320 | // 移除
321 | container.removeChild(prevVNode.el)
322 | }
323 | }
324 | break
325 | }
326 | break
327 | }
328 | }
329 |
330 | function patchText(prevVNode, nextVNode) {
331 | // 拿到文本节点 el,同时让 nextVNode.el 指向该文本节点
332 | const el = (nextVNode.el = prevVNode.el)
333 | // 只有当新旧文本内容不一致时才有必要更新
334 | if (nextVNode.children !== prevVNode.children) {
335 | el.nodeValue = nextVNode.children
336 | }
337 | }
338 |
339 |
--------------------------------------------------------------------------------