Vue3_14(虚拟DOM | 三大核心系统:Compiler、Runtime、Reactivity)

虚拟DOM的优势

对真实的元素节点进行抽象,抽象成VNode(虚拟节点),方便对其进行各种操作:

  直接操作DOM来说是有很多的限制的,比如diff、clone等等;
  可以使用JavaScript来表达非常多的逻辑,而对于DOM本身来说是非常不方便的;

其次是方便实现跨平台,包括你可以将VNode节点渲染成任意你想要的节点:

  如渲染在canvas、WebGL、SSR、Native(iOS、Android)上;
  并且Vue允许你开发属于自己的渲染器(renderer),在其他的平台上渲染;

虚拟DOM的渲染过程

 

VUE三大核心系统 

  Compiler模块:编译模板系统;
  Runtime模块:也可以称之为Renderer模块,真正渲染的模块;
  Reactivity模块:响应式系统;

 

 

简洁版的Mini-Vue框架,该Vue包括三个模块:渲染系统模块;可响应式系统模块;应用程序入口模块;

渲染系统实现

  功能一:h函数,用于返回一个VNode对象;
  功能二:mount函数,用于将VNode挂载到DOM上;
  功能三:patch函数,用于对两个VNode进行对比,决定如何处理新的VNode;

h函数的实现:直接返回一个VNode对象即可

1
2
3
4
5
6
7
const h = (tag, props, children) => {
  return {
    tag,
    props,
    children
  }
} 

mount函数的实现:
  第一步:根据tag,创建HTML元素,并且存储到vnode的el中;
  第二步:处理props属性 
    如果以on开头,那么监听事件;
    普通属性直接通过 setAttribute 添加即可;
  第三步:处理子节点
    如果是字符串节点,那么直接设置textContent;
    如果是数组节点,那么遍历调用 mount 函数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const mount = (vnode, container) => {
  const el = (vnode.el = document.createElement(vnode.tag))
 
  if (vnode.props) {
    for (const key in vnode.props) {
      const value = vnode.props[key]
      if (key.startsWith('on')) {
        el.addEventListener(key.slice(2).toLowerCase(), value)
      } else {
        el.setAttribute(key, value)
      }
    }
  }
 
  if (vnode.children) {
    if (typeof vnode.children === 'string') {
      el.textContent = vnode.children
    } else {
      vnode.children.forEach((item) => {
        mount(item, el)
      })
    }
  }
  container.appendChild(el)
}

Patch函数 – 对比两个VNode

n1和n2是不同类型的节点:
  找到n1的el父节点,删除原来的n1节点的el;
  挂载n2节点到n1的el父节点上;
n1和n2节点是相同的节点:
  处理props的情况
    先将新节点的props全部挂载到el上;
    判断旧节点的props是否不需要在新节点上,如果不需要,那么删除对应的属性;
  处理children的情况
    如果新节点是一个字符串类型,那么直接调用 el.textContent = newChildren;
    如果新节点不同一个字符串类型:
      旧节点是一个字符串类型
        将el的textContent设置为空字符串;
        旧节点是一个字符串类型,那么直接遍历新节点,挂载到el上;
      旧节点也是一个数组类型
        取出数组的最小长度;
        遍历所有的节点,新节点和旧节点进行path操作;
        如果新节点的length更长,那么剩余的新节点进行挂载操作;
        如果旧节点的length更长,那么剩余的旧节点进行卸载操作;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
const patch = (n1, n2) => {
  if (n1.tag !== n2.tag) {
    const p = n1.el.parentNode
    p.removeChild(n1.el)
    mount(n2, p)
  } else {
    const el = (n2.el = n1.el)
 
    //对象diff
    const oldProps = n1.props || {}
    const newProps = n2.props || {}
 
    //新对象覆盖原对象,新增或更新key
    for (const key in newProps) {
      const oldValue = oldProps[key]
      const newValue = newProps[key]
      if (oldValue != newValue) {
        if (key.startsWith('on')) {
          el.addEventListener(key.slice(2).toLowerCase(), newValue)
        } else {
          el.setAttribute(key, newValue)
        }
      }
    }
    //原对象对比新对象,删除多出key
    for (const key in oldProps) {
      if (key.startsWith('on')) {
        const value = oldProps[key]
        el.removeEventListener(key.slice(2).toLowerCase(), value)
      }
 
      if (!(key in newProps)) {
        // if (key.startsWith('on')) {
        //   el.removeEventListener(key.slice(2).toLowerCase(), value)
        // } else {
        el.removeAttribute(key)
        // }
      }
    }
 
    const oldChildren = n1.children || []
    const newChildren = n2.children || []
 
    // 情况一: newChildren本身是一个string
    if (typeof newChildren === 'string') {
      if (typeof oldChildren === 'string') {
        if (newChildren != oldChildren) {
          el.textContent = newChildren
        }
      } else {
        el.textContent = newChildren
      }
    } else {
      // 情况二: newChildren本身是一个数组
      if (typeof oldChildren === 'string') {
        el.textContent = ''
        newChildren.forEach((item) => {
          mount(item, el)
        })
      } else {
        //数组diff
        const oldLen = oldChildren.length
        const newLen = newChildren.length
        const commonLen = Math.min(oldLen, newLen)
 
        // oldChildren: [v1, v2, v3, v8, v9]
        // newChildren: [v1, v5, v6]
        //前面有相同节点的原生进行patch操作
        for (let i = 0; i < commonLen; i++) {
          patch(oldChildren[i], newChildren[i])
        }
 
        //新数组 比 旧数组多,则新增新
        if (oldLen < newLen) {
          newChildren.slice(commonLen).forEach((item) => mount(item, el))
        }
 
        //新数组比旧数组少,则删除旧
        if (oldLen > newLen) {
          oldChildren
            .slice(commonLen)
            .forEach((item) => el.removeChild(item.el))
        }
      }
    }
 
    // console.log(el)
  }
}

可响应式系统

1、依赖收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Dep {
  constructor() {
    this.subscribes = new Set()
  }
  depend() {
    if (activeEffect) {
      this.subscribes.add(activeEffect)
    }
  }
  notify() {
    this.subscribes.forEach((effect) => effect())
  }
}
 
let activeEffect = null
function watchEffect(effect) {
  activeEffect = effect
  dep.depend()
  effect()
  activeEffect = null
}
 
const dep = new Dep()
watchEffect(function () {
  console.log('effect1:', info.counter * 2, info.name)
})
dep.notify()

2、响应式系统Vue2实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const targetMap = new WeakMap()
const getDep = (target, key) => {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Dep()
    depsMap.set(key, dep)
  }
  return dep
}
 
//vue2对raw进行数据劫持
const reactive = (raw) => {
  Object.keys(raw).forEach((key) => {
    const dep = getDep(raw, key)
    let value = raw[key]
    Object.defineProperty(raw, key, {
      get() {
        dep.depend()
        return value
      },
      set(newValue) {
        value = newValue
        dep.notify()
      }
    })
  })
  return raw
}

3、响应式系统Vue3实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// vue3对raw进行数据劫持
const reactive = (raw) => {
  return new Proxy(raw, {
    get(target, key) {
      const dep = getDep(target, key)
      dep.depend()
      return target[key]
    },
    set(target, key, newValue) {
      const dep = getDep(target, key)
      target[key] = newValue
      dep.notify()
    }
  })
}

完整响应式系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
let activeEffect = null
 
const targetMap = new WeakMap()
const getDep = (target, key) => {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Dep()
    depsMap.set(key, dep)
  }
  return dep
}
 
// vue2对raw进行数据劫持
// const reactive = (raw) => {
//   Object.keys(raw).forEach((key) => {
//     const dep = getDep(raw, key)
//     let value = raw[key]
//     Object.defineProperty(raw, key, {
//       get() {
//         dep.depend()
//         return value
//       },
//       set(newValue) {
//         value = newValue
//         dep.notify()
//       }
//     })
//   })
//   return raw
// }
 
// vue3对raw进行数据劫持
const reactive = (raw) => {
  return new Proxy(raw, {
    get(target, key) {
      const dep = getDep(target, key)
      dep.depend()
      return target[key]
    },
    set(target, key, newValue) {
      const dep = getDep(target, key)
      target[key] = newValue
      dep.notify()
    }
  })
}
 
function watchEffect(effect) {
  activeEffect = effect
  effect()
  activeEffect = null
}
 
class Dep {
  constructor() {
    this.subscribes = new Set()
  }
  depend() {
    if (activeEffect) {
      this.subscribes.add(activeEffect)
    }
  }
  notify() {
    this.subscribes.forEach((effect) => effect())
  }
}
 
const info = reactive({ counter: 100, name: 'why' })
 
// watchEffect1
watchEffect(function () {
  console.log('effect1:', info.counter * 2, info.name)
})
 
// watchEffect2
// watchEffect(function () {
//   console.log('effect2:', info.counter * info.counter)
// })
 
// // watchEffect3
// watchEffect(function () {
//   console.log('effect3:', info.counter + 10, info.name)
// })
 
// watchEffect(function () {
//   console.log('effect4:', foo.height)
// })
 
// info.counter++
info.name = 'why123'
 
// foo.height = 2

为什么Vue3选择Proxy呢?

Object.definedProperty 是劫持对象的属性时,如果新增元素:
  那么Vue2需要再次 调用definedProperty,而 Proxy 劫持的是整个对象,不需要做特殊处理;
修改对象的不同:
  使用 defineProperty 时,我们修改原来的 obj 对象就可以触发拦截;
  而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截;
Proxy 能观察的类型比 defineProperty 更丰富
  has:in操作符的捕获器;
  deleteProperty:delete 操作符的捕捉器;
  等等其他操作;
Proxy 作为新标准将受到浏览器厂商重点持续的性能优化;
缺点:Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9

应用程序入口  

  createApp用于创建一个app对象;
  该app对象有一个mount方法,可以将根组件挂载到某一个dom元素上;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function createApp(rootComponent) {
  return {
    mount(selector) {
      const container = document.querySelector(selector)
      let isMounted = false
      let oldVNode = null
 
      watchEffect(function () {
        if (!isMounted) {
          oldVNode = rootComponent.render()
          mount(oldVNode, container)
          isMounted = true
        } else {
          const newVNode = rootComponent.render()
          patch(oldVNode, newVNode)
          oldVNode = newVNode
        }
      })
    }
  }
}

  

posted @   Hexrui  阅读(823)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
返回顶部
点击右上角即可分享
微信分享提示