├── 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 | --------------------------------------------------------------------------------