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 } }) } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!