您的位置:首页 > 财经 > 金融 > 个人住房公积金贷款_企业运营管理系统_百度云资源共享_今日头条新闻头条

个人住房公积金贷款_企业运营管理系统_百度云资源共享_今日头条新闻头条

2024/11/18 10:34:53 来源:https://blog.csdn.net/weixin_43822185/article/details/143226843  浏览:    关键词:个人住房公积金贷款_企业运营管理系统_百度云资源共享_今日头条新闻头条
个人住房公积金贷款_企业运营管理系统_百度云资源共享_今日头条新闻头条

前言

挂载更新渲染器 的核心功能,也是渲染器应该要提供的基本功能,而 挂载更新 又是基于 VNode 虚拟节点的,因为 VNode 节点描述了其对应的 真实 DOM 应该是什么样子的。

挂载与卸载

VNode 节点

无论是 vue 还是 react 都引入了 虚拟 DOM,只不过它们定义 虚拟 DOM 的结构不同,但本质上都只是一个普通的 JavaScript 对象。

VDOMVNode 是从 本质上 看是一个东西,因为 VDOMVNode 节点组成,每个 VNode 节点也能代表局部 VDOM,上篇文章中也提到过:VNodeVDOM 是可以互换的。

但从 整体上 看显然 VDOM 是包含或者等于 VNode,也就是说从严格意义上来讲,它们并不是一直相等的,取决于你的 VNode 节点的个数,如果它的节点数量是 1 那么它们是相等的。

不过现在所谈论的 VNode 就是 VDOM,谈论的 VDOM 就是 VNode,这只不过是一个简单的概念,不必过于纠结。

下面是 Vue3.x 中定义最基本的 VNode 结构:

  • vnode.type 是节点类型:标签、文本、注释、Fragment、Component 等
  • vnode.props 是节点属性数据:HTML Attributes 和 DOM Properties
const vnode = {__v_isVNode: true,__v_skip: true,type,props,key: props && normalizeKey(props),ref: props && normalizeRef(props),scopeId: currentScopeId,slotScopeIds: null,children,component: null,suspense: null,ssContent: null,ssFallback: null,dirs: null,transition: null,el: null,anchor: null,target: null,targetAnchor: null,staticCount: 0,shapeFlag,patchFlag,dynamicProps,dynamicChildren: null,appContext: null} as VNode

设置正确的元素属性

HTML Attributes 和 DOM Properties

  • HTML Attributes 指的就是定义在 HTML 标签上的属性,如:id="app"、type="text"、value="hello world" 等等

  • DOM Properties 指的是通过 JavaScript 来访问真实 DOM 元素时能够访问到的属性,很多 HTML Attributes 都能在 DOM Properties 上存在同名属性(如:el.id、el.title)等,不同名属性(如:el.className、el.textContext)等

  • 核心原则:HTML Attributes 的作用是设置 DOM Properties初始值

    dom.png

正确处理普通的 props

  • 通过 in 操作符判断 props.key 是否存在 el(即 DOM Properties)
    • 存在 则优先设置 DOM Properties,即 el[props.key] = props.value
    • 不存在 则通过 el.setAttribute(key, value) 完成属性设置
    • 针对 只读 属性的 DOM Properties,不能直接进行赋值,因此也必须转换为 el.setAttribute(key, value) 的处理,如:<input form="form1"> 中的 form 属性就是只读属性
      源码中抽离了 shouldSetAsProp 用于去判断是否可通过 DOM Properties 去更新:

在这里插入图片描述

特殊处理 class

Vue.jsclass 做了增强:

  • 指定 class 为普通 字符串
  • 指定 class 为一个 对象
  • 指定 class 为包含上述两种类型的 数组

由于 class 的值以多种形式存在,因此需要对 class 进行一些特殊处理,将 class 的值统一为字符串的形式,因为 HTML 只接收这样的 class

源码中通过 normaliz 处理不同的 class 类型,并统一返回字符串形式:

在这里插入图片描述

选择设置 class 最合适的方式

浏览器中设置 class 的方式有三种:el.className、el.classList、el.setAttribute,既然有多种方式,那么在选择时肯定要选择最优的设置方式,而其中最优的方式就是 el.className

可以做个小测试,时间不一定准确,但是差值却很明显:

const body = document.documentElement;console.time('className:')
for (let i = 0; i < 1000; i++) {body.className += i;
}
console.timeEnd('className:')console.time('setAttribute:')
for (let i = 0; i < 1000; i++) {body.setAttribute('class', body.className + ' ' + i); 
}
console.timeEnd('setAttribute:')console.time('classList:')
for (let i = 0; i < 1000; i++) {body.classList.add(i+''); 
}
console.timeEnd('classList:')// 输出结果:
className:: 5.760009765625 ms
setAttribute:: 651.76611328125 ms
classList:: 1750.427978515625 ms

事件处理

区分事件

在虚拟 DOM 中,事件可以被看作是一种特殊的属性,在 vue 中约定 vnode.props 对象中,凡是以字符串 on 开头的属性都视为 事件.

const vnode = {type: 'div',props: {onClick: () => {alert('hello');}},children: 'click here'
}

注册和更新事件

注册事件 通过 el.addEventListener 的方式进行注册即可,那如何实现 更新事件 呢?

最简单的方法:

  • 移除 之前的事件处理函数
  • 重新绑定 新的事件处理函数

但这种方式并不是最优的方式,毕竟需要来回 移除、注册 才能实现事件更新,有没有什么方法是可以只注册一次事件,也能实现事件更新的方式呢?

确实有,vue 中也是这么设计的:

  • 伪造一个事件处理函数 invoker.value,将真正的事件处理函数设置为 invoker.value 属性的值
  • 事件绑定时,先从 el._vei 读取对应的 invoker,若不存在,则将伪造的 invoker 作为事件处理函数,并将它缓存到 el._vei 属性中
  • 将真正的事件处理函数赋值给 invoker.value 属性,把伪造的 invoker 函数作为事件处理函数绑定到元素上
  • 事件触发时,实际上执行的是伪造的 invoker 函数,而 invoker 事件处理函数中会执行 invoker.value() 即 真正的事件处理函数
  • 事件需要进行更新时,直接将 invoker.value 的值重新赋值即可,不需通过 removeEventListener 移除事件
  • 当然若事件更新时确实属于事件移除操作,则还是需要通过 removeEventListener 移除事件

源码如下:

在这里插入图片描述

挂载节点

通过 patch(n1, n2, container, anchor = null, ...) 函数的初次调用实现元素挂载:

  • 首次调用 patch 函数时,n1 = null 因为是挂载阶段,因此没有旧 vnode,当 patch 函数执行时,会递归调用 mountElement 函数完成挂载
  • 第三个参数 anchor 是挂载点,最终通过 insertBefore 插入到文档中

在挂载过程中还会触发不同生命周期钩子的执行,具体的内容就不在详细进行分析了,感兴趣的可自行阅读源码

卸载操作

卸载操作实际上是发生在更新阶段,这里的更新时指,在初次挂载完成之后,后续渲染还会触发更新,只不过新 vnode 会变成 null,从而进入卸载阶段:

  • 容器的内容可能是一个或多个组件渲染的,当卸载发生时,应该正确地调用这些组件的 beforeUnmount、unmounted 等生命周期函数
  • 即使内容不是由组件渲染的,有的 元素上存在自定义指令 等,也应该要在卸载操作发生时,正确地执行对应的指令钩子函数
  • 同时需要移除绑定在 DOM 元素上的事件处理函数

基于以上原因,卸载不能简单的通过 innerHTML 来完成卸载操作,源码中通过 unmount 函数,以及一些对应移除函数实现卸载操作

在这里插入图片描述

更新子节点最佳方式

对于一个元素来说,其子节点拥有以下 3 种情况:

  • 没有子节点,即 vnode.children = null
  • 子节点是 文本节点,即 vnode.children 的值为字符串
  • 其他情况,无论是单个子元素,还是多个子节点(可能存在文本和元素的混合),都可以用数组来表示,即 vnode.children = [...]

有了规范化的子节点类型,那就可以总结更新子节点时的全部可能:

在这里插入图片描述

而在的实际的代码中,并不需要罗列去处理以上的所有情况,而更新方式必然也不是采用 “笨方式”卸载所有子节点,在挂载所有新节点,更好的做法是,通过 Diff 算法比较新旧两组子节点,试图最大程度复用 DOM 元素。

具体的 diff 算法,会在下一篇文章中进行介绍,并且会对比 vue2vue3 中的 diff 算法。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com