虚拟DOM

优势如下:

  1. 性能更高, 方便对节点进行操作, 减少因重排/重绘产生的内存浪费
  2. 各平台兼容, 可跨平台, 只要将 VDOM -> 对应平台的节点 就行了

在 Vue 中可以导入 h 函数创建, 当然结合 render() 使用才有意义

  1. import { h, createApp } from 'vue'
  2. const App = {
  3. render() {
  4. return h('div', {class: 'myCom'}, 'Good')
  5. }
  6. /*
  7. 上面相当于 template: `<div class="myCom"> Good </div>`
  8. */
  9. }
  10. createApp(App).mount('#app')

根据尤大佬的说法, h() 只是返回一个对象而已, 相当于 AST 中单节点的对象

  1. function h(tag: string, attrs?: string|object, children?: string|tuple) {
  2. return {
  3. tag,
  4. attrs,
  5. children
  6. }
  7. }

真正重要的其实是 mount()[将虚拟DOM变成真实DOM] 和 patch() [比较新旧虚拟DOM]

  1. function mount(vdom, parentDom) {
  2. if(typeof vdom === 'string') {
  3. const text = document.createTextNode(vdom)
  4. parentDom.appendChild(text)
  5. return true
  6. }
  7. if(!(vdom instanceof VDOM)) return new Error('vdom is not VDOM or String')
  8. if(!(parentDom instanceof HTMLElement)) return new Error('parentDom is not HTMLElement')
  9. const { tag, attrs, children } = vdom
  10. let dom = vdom.el = document.createElement(tag) // tag
  11. for(const key in attrs) {
  12. const value = attrs[key]
  13. if(key.startsWith("on")) {
  14. dom.addEventListener(key.silce(2).toLowerCase(), value)
  15. } else {
  16. dom.setAttribute(key, value)
  17. }
  18. } // attrs
  19. if(children) {
  20. if(typeof children === 'string') { dom.textContent = children }
  21. else {
  22. children.forEach(child => {
  23. mount(child, dom)
  24. })
  25. }
  26. } // children
  27. parentDom.appendChild(dom)
  28. }

patch() 在我看来是最蛋疼的, 因为涉及对象与对象/数组与数组的最优解, 也就是 diff(差) 算法

  1. // 必须以新旧替换最小消耗为目标
  2. function patch(oldVdom, newVdom) {
  3. // 1. 比较在字符串
  4. if(oldVdom.tag === newVdom.tag) {
  5. const el = newVdom.el = oldVdom.el // 感觉没什么必要赋值给newVdom.el
  6. // 2. 比较 attrs 对象
  7. const oldAttrs = oldVdom.attrs ?? {}
  8. const newAttrs = newVdom.attrs ?? {}
  9. // 添加所有新属性
  10. for(const key in newAttrs) {
  11. const oldValue = oldAttrs[key]
  12. const newValue = newAttrs[key]
  13. if(newValue !== oldValue) {
  14. if(key.startsWith("on")) {
  15. el.addEventListener(key.silce(2).toLowerCase(), newValue)
  16. } else {
  17. el.setAttribute(key, newValue)
  18. }
  19. }
  20. }
  21. // remove 多余属性
  22. for(const key in oldAttrs) {
  23. if(!(key in newAttrs)) {
  24. if(key.startsWith("on")) {
  25. el.removeEventListener(key.silce(2).toLowerCase(), oldAttrs[key])
  26. } else {
  27. el.removeAttribute(key)
  28. }
  29. }
  30. }
  31. // 3. 比较 children 对象 ---> Array|String VS Array|String
  32. const oldChild = oldVdom.children ?? []
  33. const newChild = newVdom.children ?? []
  34. if(typeof newChild === 'string') {
  35. if(typeof oldChild === 'string') {
  36. if(newChild !== oldChild) el.textContent = newChild
  37. } else {
  38. el.children.forEach( child => {
  39. el.removeChild(child.el)
  40. })
  41. el.textContent = newChild
  42. }
  43. } else {
  44. if(typeof oldChild === 'string') {
  45. el.innerHTML = ''
  46. newChild.forEach(child => {
  47. mount(child, el)
  48. })
  49. } else {
  50. const commonLength = Math.min(oldChild.length, newChild.length)
  51. for(const key in commonLength) {
  52. patch(oldChild[key], newChild[key]) // 并非最优解
  53. }
  54. if(newChild.lnegth > commonLength) {
  55. newChild.slice(commonLength).forEach(child => {
  56. mount(child, el)
  57. })
  58. } else {
  59. oldChild.slice(commonLength).forEach(child => {
  60. el.removeChild(child.el)
  61. })
  62. }
  63. }
  64. }
  65. } else {
  66. const parent = oldVdom.el.parentNode // parentElement [ie专用], parentNode [w3c标准]
  67. parent.removeChild(oldVdom.el)
  68. mount(newVdom, parent)
  69. }
  70. }

三大核心模块

Render

  • runtime-core

    complier

  • complier-core

    reactive

  • 其实就是依赖收集+观察者模式(proxy and Reflect)

    阅读源码相关内容