Skip to Content
Vue 源码01.Vue设计思想

Vue 的设计思想

在具体讲解 Vue3 源码之前,其实最重要的,大家需要对整个 Vue3 的设计有一个整体上的认知,为什么要设计成这个样子,具体的原因是什么,这样我们才会对框架的定位和方向有一个全局性的了解,在学习后续的模块的时候,才会了解为什么要做这样的的模块设计和拆分,不然我们就会被代码的细节给困住,看不清楚全貌

命令式和声明式

首先什么命令式的代码,比如我们在使用原生 DOM 的时候要实现一个元素点击的功能,会写出下面的代码:

const div = document.querySelector("#layer"); // 获取div元素 div.innerText = "hello world"; // 设置文本内容 div.addEventListener("click", () => { alert("hello"); }); // 绑定点击事件

代码描述的过程,和我们思维的顺序是同步的,一步一步去实现,做事的过程是清晰的

声明式框架和命令式不一样,很明显命令式更加关注过程,而声明式框架关注的是结果,比如,上面的代码,使用 vue 去实现,可能就是下面这个样子

<div @click="() => alert('hello')">hello world</div>

通过这一行代码,我们很明显的可以看出代码想干什么事情,我们知道了要实现元素点击事件的这个结果,但是至于底层是怎么实现这个结果的,我们并不需要关心。

其实很明显,对于平时开发来说,第二种方式大大的减少程序员的心智负担。

当然要注意一个问题,我这里并不是说声明式的代码方式就比命令式的代码好,很多时候,是框架开发者的一种选择和一种权衡。

命令式代码麻烦,但是实现过程清晰,声明式代码简单,但是实现过程对开发者而言不可见,对框架掌握不熟悉的开发者,如果出现了错误,可能反而会不知道如何处理。

当然我也说了这事一种权衡,一两段这种代码我们会觉得命令式代码更符合我们的思维过程,但是当我们代码越来越多,工程越来越臃肿,事情又会转向另外一种极端,命令式代码的可维护性会变得非常困难

所以,这也是 vue 采用声明式代码的一种权衡抉择,当然了,为了让开发人员对框架更加可控,vue 也留出了让开发人员自己去实现的一些 API

采用虚拟 DOM

正是由于使用了声明式代码的方式,在处理元素的挂载或者更新的时候,很明显是更加费事的,如果原生 DOM 需要更新元素节点内容,可能只需要执行

div.textContent = "hello vue3";

但是声明式代码由于是自定义的,我们还需要找出和原来代码的差异性,然后再进行更新操作。

直接的 DOM 操作,无论是查看,比较,删除或者移动等等,都会对浏览器性能有比较大的消耗

所以,Vue 使用虚拟 DOM 的方式,其实也就是大家熟知的 js 对象。下面就是 Vue3 代码中初始化的 vnode 对象

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, targetStart: null, targetAnchor: null, staticCount: 0, shapeFlag, patchFlag, dynamicProps, dynamicChildren: null, appContext: null, ctx: currentRenderingInstance, };

通过使用对象的方式来描述真实 DOM,在处理的时候,先对 JS 对象进行比较,发现有不同的地方,然后再去操作真实 DOM,尽可能的减少真实 DOM 的操作。

其实通过上面简单描述,大家也知道了有了虚拟 DOM 之后反而多加了一层操作,在性能上其实是要差与原生 DOM 的操作的。

但是这同样需要权衡,需要知道在什么样的代码环境之下。当代码量达到一定程度之后,我们并不能简单以理论化的数据来衡量,还需要对比开发速度,代码量,代码的整体架构以及代码修改的情况。

在 Vue3 的相关算法的加持下,虚拟 DOM 对性能的影响已经降低到很小,但是对比可维护性和对开发人员的心智负担的影响,比原生 DOM 操作提高的非常非常多。所以这也是一种权衡的选择

运行时和编译时

正是由于使用虚拟 DOM 和声明式代码,所以,什么时候去转换就是一个问题,什么意思呢?

由于有了虚拟 DOM,我们肯定需要调用相关的渲染方法,将虚拟 DOM 渲染成真实 DOM,这样最终才能显示出来

但是写代码的时候又是声明式的,所以还需要将模板想办法转换成虚拟 DOM

vue3 为了性能和灵活性的考虑,采用了运行时+编译时的架构。使用编译时将声明式的 HTML 标签编译成树状结构的数据对象,也就是虚拟 DOM。然后在运行时,调用相关渲染函数,将虚拟 DOM 渲染成真实 DOM

Vue 源码

直接进入 vuejs 在 github 上的地址:https://github.com/vuejs/core,源码是Monorepo的结构

image-20241116145553207

其实从 Vue3 的源码中可以看出,整个源码的核心四大块:

  • 响应式系统
  • 运行时
  • 编译时
  • 服务器端渲染(略)

由于现在版本很高,Vue3 的边界,代码的完整度,TS 类型的完整度,各个大佬提供的 Issues 各种各样的代码都已经集成在里面,如果我们希望直接读取现在的代码学习源码的话,就太难了,我们可以直接回退到一开始 vue3 刚刚发布的代码版本,看看 vue3 一开始的样子:

可以点击 Tags 标签,找到对应的链接https://github.com/vuejs/core/tags?after=v3.0.0

并且找到对应的 commit hash --- d8c1536

当然,直接通过 git 命令获取

git rev-list --max-count=1 v3.0.0 d8c1536ead56429f21233bf1fe984ceb3e273fe9

通过 commit hash,我们可以 checkout 到 v3.0.0 版本

git checkout d8c1536ead56429f21233bf1fe984ceb3e273fe9

在这个分支下面,可以很明显的看出代码量的区别

Ecma262 文档

地址:https://github.com/tc39/ecma262

克隆后,执行npm install来安装环境。然后可以执行npm run build来构建。结果将显示在out目录中。

Last updated on