梦应逍遥 发表于 2024-10-4 20:21:27

Vue3 核心源码解析

Vue 发展进程

https://i-blog.csdnimg.cn/direct/547c01d4a1f54c7eb24f94b0592c9635.png
   虚拟DOM 为后续做服务端渲染以及跨端框架位置提供根本。 
相对应地缺点:https://i-blog.csdnimg.cn/direct/1f274a4ed27d4f11bf9cec1f1e41a081.png
https://i-blog.csdnimg.cn/direct/f4a44981602a488bba5669c0f7d290c7.png
(1)源码优化:

对于 Vue.js 框架本身开辟的优化。
目的:让代码更易于开辟和维护 
源码的优化主要体现在使用 monorepo和TypeScript 管理和开辟源码。
这样做的目的是提拔自身代码可维护性 

①更好的代码管理方式: monorepo 

https://i-blog.csdnimg.cn/direct/d2216fa7fefa4cd7a216287ad639a87b.pnghttps://i-blog.csdnimg.cn/direct/491ec584f453410091a1756fc8daf4b2.png
相对于 Vue.js 2.x的源码组织方式,monorepo 把这些模块拆分到不同的 package 中,每个 package 有各自的 API、类型界说和测试。
这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确开辟职员也更容易阅读、明白和更改全部模块源码,提高代码的可维护性。 
例:package(比如 reactivity 响应式库)是可以独立于 Vue.js 使用的。
这样用户如果只想使用 Vue.js 3.0的响应式能力,可单独依赖这个响应式库。而不消去依赖整个 Vue.js,减小了引用包的体积巨细,而 Vue.js2.x是做不到这一点的 
 
②有类型的 JavaScript: TypeScript

——>更有利于代码的维护 
https://i-blog.csdnimg.cn/direct/d91b29ad1c344ab5901879550b4b0b9b.png 


[*]因为它可以在编码期间帮你做类型检查  避免一些因类型题目导致的错误
[*]有利于它去界说接口的类型,利于IDE(集成开辟环境  常见的IDE有Visual Studio、Eclipse、IntelliJ IDEA等)对变量类型的推导
https://i-blog.csdnimg.cn/direct/a69799f37af84182b7a0cff7bca7b378.png
   

[*]Flow 是 Facebook出品的 JavaScript 静态类型检查工具,它可以以非常小的成本对已有的JavaScript 代码迁入,非常机动,这也是Vue.is 2.0 当初选型它时一方面的考量。
[*]Flow 对于一些复杂场景类型的检查,支持得并不好。(在看 Vue.js 2.x源码的时候,在某行代码的注释中看到了对 Flow 的吐槽)https://i-blog.csdnimg.cn/direct/2ce82298cf7741fcbae6427ebdb92287.png
因此就选用了 TypeScript  举行整个项目的重构,TypeScript 提供了更好的类型检查,能支持复杂类型的推导。源码接纳TS编写,也省去了维护所在TS文件的麻烦。
 

(2)性能优化:

对于 Vue.js 2.x 已经足够优秀的前端框架 它的性能优化可以从哪些方面举行突破呢? 
 
①源码体积优化: 

   原理:JS包体积越小, 即网络传输时间越短,JS引擎解析包的速度越快。
https://i-blog.csdnimg.cn/direct/43e8959ee2214a779900247848870eb7.png 
https://i-blog.csdnimg.cn/direct/6952c26f37974f38b255d295ca210ec9.png
https://i-blog.csdnimg.cn/direct/7939e8d1151b42db94b3fa244ee289a3.png
https://i-blog.csdnimg.cn/direct/f3e16403539043cd805f89a41df56e9c.png
https://i-blog.csdnimg.cn/direct/6d7f1d20dd654dc8a50fb64a74f7f668.png 
   原理: 未被引入的square模块被标志,压缩阶段会使用压缩工具真正删除没用到的代码。
https://i-blog.csdnimg.cn/direct/76a91b23813044119dfe4b43da8f5804.png
 
②数据劫持优化: 

Vue.js 区别于 React.js 的一大特色是Vue的数据是响应式的。 
https://i-blog.csdnimg.cn/direct/899b20a8ac7a4c8d8d36a8e62ccc2770.pngDOM 是数据的一种映射, 数据发生变化后可以自动更新DOM,用户只需要专注于数据的修改,没有别的的性质负担。但是这样子的功能实现必须需要劫持数据的访问与更新。
   当数据改变后,为了自动更新 DOM,那么就必须劫持数据的更新。
也就是说当数据发生改变后能自动实行一些代码去更新 DOM。 
Vue.js 怎么知道更新哪一片 DOM 呢?
因为在渲染 DOM 的时候访问了数据,我们可以对它举行访问劫持。
这样就在内部创建了依赖关系,也就知道数据对应的 DOM 是什么了 。内部需要依赖watcher的数据结构做依赖管理
https://i-blog.csdnimg.cn/direct/be9145f863ca4deabafe6803df6d5af0.pngVue1 和 Vue2  通过 object.defineProperty 举行数据劫持:
https://i-blog.csdnimg.cn/direct/0410fcce763242e9ad59bf3419e5104b.png
   缺陷:必须知道要拦截的key是什么,因此不能检测对象的属性的添加和删除。 
https://i-blog.csdnimg.cn/direct/89275a5523a14836bdabeec28f0d9382.png
如果要劫持内部深层次的对象变化,就需要递归遍历这个对象,通过Object.defineProperty把每一层对象的数据都变成响应式的。——> 响应式数据过于复杂,就会有相称大的性能负担。
https://i-blog.csdnimg.cn/direct/742d89f2810f421d98f2061719331892.png
劫持了一整个对象,因此对对象属性的增长和删除都能够检测到。 
https://i-blog.csdnimg.cn/direct/d45de82b9d744a07ab20b4cf06aa851c.png这样的利益是真正访问到的内部对象才会变成响应式,而不是无脑递归这样无疑也在很大程度上提拔了性能。
 

(3)编译优化:Block tree

https://i-blog.csdnimg.cn/direct/d2320c086e5245c2be2fe33988cebe10.png 响应式过程发生在new Vue到 init 阶段。
https://i-blog.csdnimg.cn/direct/b96474d940584a01a34e39cbfc016c00.pnghttps://i-blog.csdnimg.cn/direct/096d8da46c084393a836214f155f5d4a.png
https://i-blog.csdnimg.cn/direct/8157c6df01ad470ebf1afd0f0abc39d7.png
   虽然 Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的:,虽然Vue能保证触发更新的组件最小化,但是在单个组件内部,依然需要遍历该组件的整个 windows 树。
https://i-blog.csdnimg.cn/direct/62c3cd52863d4b51a621811c957ccfbe.png
https://i-blog.csdnimg.cn/direct/22c36ae5413243b1ad8dc62d98416304.png
代码只有一个动态节点,很多的 diff 和遍历其实是不需要的,导致Windows 性能与模板巨细正相关,跟动态节点的数目无关。当一些组件整个模板只有少量动态节点时,这些遍历都是性能的浪费。 
理想状态只需要 diff  这个绑定 message 动态节点的p标签即可 
https://i-blog.csdnimg.cn/direct/86a60dc4db154dd1be8b57538475260f.png
   Block tree


[*]Blocktree 是一个将模版基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的每个区块只需要以一个 Array 来追踪自身包罗的动态节点。
[*]借助 Block tree,Vue.js 将vnode 更新性能由与模版整体巨细相关提拔为与动态内容的数目相关
https://i-blog.csdnimg.cn/direct/aeff5d14caef4329af162f628f1a9c6b.png 

(4)语法API优化: Composition API

https://i-blog.csdnimg.cn/direct/5ae33899c71a4c9ba4b20748e7328819.png
①优化逻辑组织 https://i-blog.csdnimg.cn/direct/0afd6a099514474492ae35bda8e46298.png

   Options APl


[*]OptionsAPl的设计是按照 methods、computed、data、props 这些不同的选项分类
[*]当组件小的时候,这种分类方式一清二楚; 但是在大型组件中,一个组件大概有多个逻辑关注点当使用 Options API的时候,每一个关注点都有本身的 Options
[*]如果需要修改一个逻辑点关注点,就需要在单个文件中不停上下切换和寻找
https://i-blog.csdnimg.cn/direct/31c42b27ee984f89b01b2211f82c23fe.pnghttps://i-blog.csdnimg.cn/direct/bbb067bd1405494e81f5c3e242bd7d0b.png
按照逻辑关注点做颜色编码, 当使用Option API 编写组件时,逻辑关注点都黑白常分散的。https://i-blog.csdnimg.cn/direct/7b01c19a49884184a6ad07a8bc1e1a56.png
https://i-blog.csdnimg.cn/direct/bb26ce3555c4453ba2a4a2381887b9cd.png
开辟项目变得复杂时,免不了需要抽象出一些复用逻辑。 
https://i-blog.csdnimg.cn/direct/ac683a3a1b0a4bff8a2a9ddc7e749ba5.png
 
 鼠标位置监听的例子:https://i-blog.csdnimg.cn/direct/9929d2e845304fae9221f5b0a06dd159.png
https://i-blog.csdnimg.cn/direct/3fa747319f4947c3a733d2e0998142a4.png
 如果有大量的Mixin就会有定名冲突和数据泉源不清楚。
   

[*]首先每个 mixin 都可以界说本身的 props、data,它们之间是无感的所以很容易界说雷同的变量,导致定名冲突。
[*]对组件而言,如果模板中使用不在当前组件中界说的变量,那么就会不太容易知道这些变量在哪里界说的,这就是数据泉源不清楚
但是Vue.js 3.0 设计的 Composition APl,就很好地资助我们办理了 mixins 的这两个问
https://i-blog.csdnimg.cn/direct/aedb575b208041e996092458c4e4ddc4.png
https://i-blog.csdnimg.cn/direct/52b7b1f72e7248ada4e95108aa9375f4.png
   Composition APl


[*]除了在逻辑复用方面有优势,也会有更好的类型支持
[*]因为它们都是一些函数,在调用函数时,自然全部的类型就被推导出来了。不像 Options API 全部的东西使用 this
[*]别的,CompositionAPl 对 tree-shaking 友好,代码也更容易压缩
 

②引入 RFC:

使每个版本改动可控
https://i-blog.csdnimg.cn/direct/5f8157879b0e496c89f4e539202bb2d8.png
 https://i-blog.csdnimg.cn/direct/d12fbecdafc146e0a8c86a0c2d6b07b5.png
过渡期: https://i-blog.csdnimg.cn/direct/1cca1df8dfbb458082bf4d45381f7a14.png
https://i-blog.csdnimg.cn/direct/3382d6761bc1422d9f7e2c5193672255.pnghttps://i-blog.csdnimg.cn/direct/e410e23a18004f96876fe9bbcecf43f9.png
 
 
组件渲染

 在 Vue.js 中,组件是一个非常重要的概念,整个应用的页面都是通过组件渲染来实现的
 编写组件开始,到最终真实的 DOM 又是怎样的一个变化过程呢?

组件: 

https://i-blog.csdnimg.cn/direct/4c53fd6fc6654ecba465068eb0b5e184.png
https://i-blog.csdnimg.cn/direct/b64e5f4fe5af425a9f12c66a0c78dc2b.png
https://i-blog.csdnimg.cn/direct/a293119466374ef3bc91f990d50968de.png①应用步伐初始化:

一个组件可以通过 “模板加对象形貌” 的方式创建,组件创建好以后是如何被调用并初始化的呢?
因为整个组件树是由根组件开始渲染的。
为了找到根组件的渲染入口,需要从应用步伐的初始化过程开始分析 
https://i-blog.csdnimg.cn/direct/d4ff56595bae49b28770ed634ac36b7f.png
   本质:把 app 组件挂载到 id 为 app 的DOM节点上。
createApp 是一个入口函数,Vue对外暴露的函数
createApp 主要是:https://i-blog.csdnimg.cn/direct/7b211ab66ccb4aa4bc59ad1ddcb4a7bc.png

[*]创建 app 对象;
[*]重启 app.mount 方法
https://i-blog.csdnimg.cn/direct/ec946fc6568743d3805abdb01df345f4.png
ensureRenderer() 用来创建一个渲染器对象
渲染器:为跨平台渲染做准备(包罗平台渲染核心逻辑的JS对象)
https://i-blog.csdnimg.cn/direct/ceafd73d89754079ba45d7fd2f2defed.png
优点:用户只依赖响应式包的时候,就不会创建渲染器,可以通过 tree-shaking 移除核心渲染逻辑相关的代码


[*]在整个 app 对象创建过程中,Vue.js 使用 闭包和函数柯里化 的本事,很好地实现了参数保留
[*]比如,在实行 app.mount 的时候,不需要传入 渲染器render(因为在实行 createAppAPI的时候渲染器 render 参数已经被保留下来了 )
先思考一下,为什么要重写 app.mount 这个方法而不把相关逻辑放在 app 对象的 mount 方法内部来实现呢? 
https://i-blog.csdnimg.cn/direct/031cb1b750184256be681dab6a4756d5.png
    标注的可跨平台的组件渲染流程:先创建Vnode,再渲染Vnode。rootCotainer可以是(DOM大概是其他平台的其他类型的值,即这些代码实行逻辑都是与平台无关的,因此需要重写方法来完满外平台下的渲染逻辑。)
https://i-blog.csdnimg.cn/direct/b0916f6d87bc4d0db193f590106b4392.png
https://i-blog.csdnimg.cn/direct/bca9c812fbb343838428e96aac141e45.png
 
②核心渲染流程:

创建 vnode 和渲染 vnode 
https://i-blog.csdnimg.cn/direct/b18e803da609445d95b2e8c51951fbde.png
1️⃣平凡元素节点:
https://i-blog.csdnimg.cn/direct/eff6d3d2ffee41be9653327b9aad02de.png https://i-blog.csdnimg.cn/direct/a712d86fbeb44aa58bd76ad8f03aa44d.png
https://i-blog.csdnimg.cn/direct/0a13f1cb69e745febf51726125f24486.png
2️⃣组件节点
https://i-blog.csdnimg.cn/direct/b91ddfe2e1da48a2ae0676565a7e6636.pnghttps://i-blog.csdnimg.cn/direct/a567087bb7e446d19df3442d9fc64f5d.png
https://i-blog.csdnimg.cn/direct/d9518767f0344b2da1910ffabef67427.png
即不会真的在页面上渲染一个 CustomComponent 标签,而是渲染组件内部界说的 HTML 标签。
3️⃣纯文本Vnode
4️⃣注释Vnode 
 
内部还针对Vnode tag做了更详细的分类,而且把VNode类型做编码,以便在后面的配置阶段,可以根据不同的类型实行相应的处理逻辑: 
https://i-blog.csdnimg.cn/direct/4a049e146efd4f3485363ddb0152c231.png 
那么 vnode 有什么优势呢?
为什么一定要设计 vnode 这样的数据结构呢? 
 https://i-blog.csdnimg.cn/direct/ebcf18f1df61421183e57b801334f8dd.png
   https://i-blog.csdnimg.cn/direct/81464f1f150649a699bd22899ef4eb87.png首先这种基于 vnode 实现的 MVVM 框架,在每次 render to vnode 的过程中,渲染组件会有一定的 JavaScript 耗时,特别是大组件
当我们去更新组件的时候,用户会感觉到显着的卡顿。
虽然 diff 算法在减少 DOM 操作方面足够优秀,但最终还是免不了操作 DOM所以说性能并不是 vnode 的优势
https://i-blog.csdnimg.cn/direct/46407cd5753a497a8ea656e16a978985.pnghttps://i-blog.csdnimg.cn/direct/679ee7db80654f0eaecf2501c4b59ea4.png
https://i-blog.csdnimg.cn/direct/31216958cf6345f0addbe34ed7e0c15a.png
   创建VNode操作:对 props 做标准化处理,对 Vnode 的类型信息编码,创建 VNode 对象,标准化子节点children 
app.mount 内部通过实行 render 函数去渲染创建的 VNode
https://i-blog.csdnimg.cn/direct/c49da32285334071908e10f4ef9f37ae.png
   https://i-blog.csdnimg.cn/direct/964184e4de7e4833924bbcdb67b57a28.pnghttps://i-blog.csdnimg.cn/direct/215a6986b09b45d99fe918e121a78e22.pngpatch 函数功能:


[*]根据 VNode 挂载 DOM
[*]根据新旧 VNode 更新 DOM 
重点关注对组件的处理、对平凡 DOM 元素 两种类型的处理节点的渲染逻辑 
https://i-blog.csdnimg.cn/direct/3d43e2b111e14e63b569471099ecda76.pnghttps://i-blog.csdnimg.cn/direct/218c3b6c3269491daf131d427a70cd0c.png
主要操作:创建组件实例, 设置组件实例,设置并运行带副作用的渲染函数https://i-blog.csdnimg.cn/direct/122154ab006944f883188181a61997f6.png
https://i-blog.csdnimg.cn/direct/abfb1161416a4a5fbe79bc110b833aa0.png初始渲染主要做两件事变: 渲染组件生成subTree(VNode对象)、把 subTree 挂载到 container 中
https://i-blog.csdnimg.cn/direct/e183746f3a0145529168e0ac38f60ea3.png
https://i-blog.csdnimg.cn/direct/b93a45fa7e154683acb855f5faa3c096.png
https://i-blog.csdnimg.cn/direct/4b7c31bdc4014a90bb69ce47f45944af.png


[*]hello节点渲染生成的Vnode就是hello对应的init Vnode(组件Vnode)
[*]hello组件内部的整个DOM节点对应的Vnode就是实行 renderComponentRoot 渲染生成对应的subTree,可以称之为子树Vnode 
每个组件都有对应的render函数 
renderComponentRoot  就是实行 render 函数,创建整个组件内部的Vnode,把这个 Vnode 再经过内部一层标准化就能得到该函数的返回效果即子树Vnode。渲染生成子树Vnode后就是大勇patch函数把子树Vnode挂载到content中。
 
对平凡DOM元素的处理流程: 
https://i-blog.csdnimg.cn/direct/c59cedf334ae490c8735f0d7836f79fb.pnghttps://i-blog.csdnimg.cn/direct/5e1d20ddc021497c8d55f9f93015f683.png
https://i-blog.csdnimg.cn/direct/f3c09a5b38324fc6b7be15cfa23ad546.png如果是其他平台比如 Weex,hostCreateElement 方法就不再是操作 DOM而是平台相关的 API了,这些平台相关的方法是在创建渲染器阶段作为参数传入的 
创建完 DOM 节点后,接下来要做的是判定如果有 props 的话给这个 DOM 节点添加相关的 class、style、event等属性,并做相关的处理这些逻辑都是在 hostPatchProp 函数内部做的 
https://i-blog.csdnimg.cn/direct/2d16c7879c464c1e9a086057d0a58619.pngDOM和VNode都是一棵树,而且结构和DOM逐一映射。 
https://i-blog.csdnimg.cn/direct/403f964398224f6da3db470863ab9c2f.pnghttps://i-blog.csdnimg.cn/direct/2521a2546b4340d6b8e0a705bc121ace.png
https://i-blog.csdnimg.cn/direct/bdd7e2d1af2f41ed9fed27a53c3affff.png
https://i-blog.csdnimg.cn/direct/141f71f5adac40a1b539be12275b5905.png
处理完全部子节点后, 通过insert的方法把创建DOM元素节点挂载到content下
因为 insert 的实行是在处理子节点后,所以挂载的顺序是先子节点,后父节点
最终挂载到最外层的容器上
在 mountChildren 的时候递归实行的是 patch 函数,而不是 mountElement 函数
这是因为子节点大概有其他类型的vnode,比如组件 vnode
https://i-blog.csdnimg.cn/direct/fa0cb2a300c543109bf2be83fc8a1856.png 
https://i-blog.csdnimg.cn/direct/a94c4d9aaa7f4d54a49c8bc1422c7335.png


[*]梳理了组件渲染的过程,本质上就是把各种类型的 vnode 渲染成真实 DOM
[*]组件是由模板、组件形貌对象和数据构成的,数据的变化会影响组件的变化
[*]组件的渲染过程中创建了一个带副作用的渲染函数
[*]当数据变化的时候就会实行这个渲染函数来触发组件的更新 
③ 副作用渲染函数更新组件的过程

https://i-blog.csdnimg.cn/direct/3e49bcf515ab4cbdaa65e27e5554fac9.png
https://i-blog.csdnimg.cn/direct/9db41e79221e47fc90b2aa812ed2b374.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Vue3 核心源码解析