1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>6_手写_将虚拟 Dom 转化为真实 Dom(类似的递归题-必考)</title>
  7. </head>
  8. <body>
  9. <script>
  10. const VDOM = {
  11. tag: 'DIV',
  12. attrs: {
  13. id: 'app'
  14. },
  15. children: ['dd', {
  16. tag: 'SPAN',
  17. children: [{
  18. tag: 'A',
  19. children: ['aa']
  20. }]
  21. },
  22. {
  23. tag: 'SPAN',
  24. children: [{
  25. tag: 'A',
  26. children: ['bb']
  27. },
  28. {
  29. tag: 'A',
  30. children: ['cc']
  31. }
  32. ]
  33. }
  34. ]
  35. };
  36. // 渲染函数
  37. function _render(vnode) {
  38. // 若是数字类型转化为字符串,因为 document.createTextNode(vnode: string)
  39. if (typeof vnode === 'number') {
  40. vnode = String(vnode)
  41. }
  42. if (typeof vnode === 'string') {
  43. // 返回文本节点对象
  44. return document.createTextNode(vnode)
  45. }
  46. // 生成普通 DOM
  47. const dom = document.createElement(vnode.tag)
  48. if (vnode.attrs) {
  49. Object.keys(vnode.attrs).forEach((key) => {
  50. const value = vnode.attrs[key]
  51. dom.setAttribute(key, value)
  52. })
  53. }
  54. // !重点 子数组进行递归操作
  55. vnode.children.forEach((child) => dom.appendChild(_render(child)))
  56. return dom
  57. };
  58. document.body.appendChild(_render(VDOM));
  59. /*
  60. !:类似面试题
  61. */
  62. const el = {
  63. tagName: 'ul',
  64. children: [
  65. 'dd',
  66. {
  67. tagName: 'li',
  68. props: {
  69. class: 'item'
  70. },
  71. children: ['aa']
  72. },
  73. {
  74. tagName: 'li',
  75. props: {
  76. class: 'item'
  77. },
  78. children: ['bb']
  79. },
  80. {
  81. tagName: 'li',
  82. props: {
  83. class: 'item'
  84. },
  85. children: ['cc']
  86. }
  87. ]
  88. }
  89. function vDOM(ele) {
  90. if (Array.isArray(ele)) {
  91. const res = []
  92. ele.forEach(item => {
  93. res.push(vDOM(item))
  94. })
  95. // 注意:此出返回的是一个数组
  96. return res
  97. } else if (Object.prototype.toString.call(ele) === '[object Object]') {
  98. const {
  99. tagName,
  100. children
  101. } = ele
  102. const tag = document.createElement(tagName)
  103. const child = vDOM(children)
  104. // 注意:需要将返回的数组进行解构,然后将数组中的 DOM 节点 append 到创建的 DOM 元素上。使用展开运算符...
  105. tag.append(...child)
  106. return tag
  107. } else {
  108. const dom = document.createTextNode(ele)
  109. return dom
  110. }
  111. }
  112. document.body.appendChild(vDOM(el));
  113. </script>
  114. </body>
  115. </html>
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>渲染器</title>
  8. </head>
  9. <body>
  10. <script>
  11. const vnode = {
  12. tag: 'div',
  13. props: {
  14. id: 'root',
  15. onClick: () => alert('hello')
  16. },
  17. children: 'click me'
  18. };
  19. function renderer(vnode, container) {
  20. const el = document.createElement(vnode.tag)
  21. // 遍历 props 对象
  22. for (const key in vnode.props) {
  23. // 重点。。。。
  24. if (/^on/.test(key)) {
  25. el.addEventListener(key.substr(2).toLowerCase(), vnode.props[key])
  26. }else {
  27. el.setAttribute(key + '', vnode.props[key])
  28. }
  29. }
  30. // 处理 children
  31. if (typeof vnode.children === 'string') {
  32. el.appendChild(document.createTextNode(vnode.children))
  33. } else if (Array.isArray(vnode.children)) {
  34. vnode.children.forEach(child => renderer(child, el))
  35. }
  36. // 将元素添加到挂载节点下
  37. container.appendChild(el)
  38. }
  39. renderer(vnode, document.body);
  40. </script>
  41. </body>
  42. </html>

涉及到的知识点

append()
appendChild()
document.createElement()
document.createTextNode()
dom.setAttribute(key, value)
Object.prototype.toString.call(ele) === '[object Object]'

append 和 appendChild 的区别

DOM 中有两个常用的方法:

  • append
  • appendChild

你知道它们的区别吗?先看 TypeScript 类型:

  1. append(...nodes: (Node | string)[]): void;
  2. appendChild<T extends Node>(node: T): T;

可以看出区别了吧?下面进行详细解读:

参数类型不同

append可插入Node节点或字符串,但 appendChild 只能插入Node节点,例如:

  • 插入节点对象 ```typescript const parent = document.createElement(‘div’) const child = document.createElement(‘p’)

parent.append(child) //

parent.appendChild(child) //

  1. - 插入字符串
  2. ```typescript
  3. const parent = document.createElement('div')
  4. parent.append('text') // <div>text</div>
  5. // Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'
  6. parent.appendChild('text')

参数个数不同

append可传入多个参数;但 appendChild只接受一个参数。

  1. const parent = document.createElement('div')
  2. const child = document.createElement('p')
  3. const childTwo = document.createElement('p')
  4. // <div><p></p><p></p>Hello world</div>
  5. parent.append(child, childTwo, 'Hello world')
  6. // <div><p></p></div>
  7. parent.appendChild(child, childTwo, 'Hello world')

返回值不同

append没有返回值,但 appendChild返回插入的节点对象:

  1. const parent = document.createElement('div')
  2. const child = document.createElement('p')
  3. const appendValue = parent.append(child)
  4. console.log(appendValue) // undefined
  5. const appendChildValue = parent.appendChild(child)
  6. console.log(appendChildValue) // <p><p>