前言:我觉得这个面试的时候比较常问,我之前背这方面的面试题很费劲,因为是死记硬背,根本不懂里面的含义。但是了解Vue大部分源码的实现思路之后,一切都豁然开朗。
不得不说这种问题确实很难,因为我花了大量时间学习Vue原理才对下面的内容完全了解
源码层面的优化
源码优化面向的是框架的开发者
,目的是让框架本身的代码更易于开发和维护。源码的优化主要体现在使用monorepo和TypeScript开发和管理源码.
Monorepo 是 "monolithic repository" 的缩写,意为"单一仓库"。它是一种代码管理策略,其中在单个版本控制仓库(如 Git 仓库)中存储多个项目或包。这些项目可能是相互关联的,或者是完全独立的。
优点:
- 可以更轻松地管理和更新仓库中所有项目的依赖
- 可以在单个仓库中构建和测试所有的项目或包,确保它们之间没有冲突
Vue2的源码统一放在src下,3则是放在package下。内部文件都是根据功能划分,但是3的功能划分力度更细致
,每个项目之间没有影响,有着自己的API,类型定义和单元测试。
例如:你只想获得Vue提供的响应式能力
,对于2来说,你不得不引入整个Vue.js.但是对于3来说,你可以只引用Vue下的reactivity响应库。
2用的是flow,3用的是typeScript。
对于typeScript,我的理解是对于框架,组件库很有必要,因为有利于IDE自动补全,并且有更清晰的架构
性能优化
源码体积优化
因为包的大小减小,传输时间就会加快,解析包的速度也会加快
- 移除了部分API,例如过滤器
- 引入树摇减小打包体积。
tree-shaking
的原理也很简单:依赖ES2015模块语法的静态结构import,export,通过编译阶段的静态分析找到没有导人的模块并打上标记然后在压缩阶段利用压缩工具删除已标记的代码。内置组件例如keepAlive没有被使用就不会被打包进来。
数据劫持优化
对于2的Object.defineProperty
- 因为需要预先知道要拦截的key是什么,所以并
不能检测对象属性的添加和删除
- 对于一个较深层级的对象来说,不管是否会访问到深层级的属性,都会进行深度监听,一次性计算量大
Vue.js3使用的proxy
不会出现上面的第一个问题。虽然对于深层级对象,仍然需要递归实现,但是它是在proxy的getter操作中赋予响应式。意味着只有访问到这个层级的属性才会建立响应
,而不是像2一样直接给整个对象都加上响应。
渲染优化
从双端Diff算法升级为快速Diff算法,具体可以看我的这两篇文章
编译优化
通过数据劫持和依赖收集,Vue2数据更新并触发重新渲染的粒度是组件级
的。
由于 Vue 使用了模板和组件的组合方式,每个组件都有自己独立的数据对象、依赖收集和渲染上下文。
这意味着数据变化只会触发当前组件内部的重新渲染
,不会影响其他组件,因此数据更新和重新渲染的粒度是组件级的。
但如果组件中有静态节点,即以后都不会发生改变,但是Diff算法还是会重新比较这些静态节点,尽管我们已经知道这些比较毫无意义。
Vue3在编译阶段对静态模板进行了分析,编译生成了BlockTree
,它会指导Diff算法的执行跳过静态节点。。
组合式APi
这个是对于用户来说改变最大的部分,一种新的代码组织逻辑。但不是Vue的编程范式
,他的出现不是为了取代选项式。
但它有着更好的代码逻辑复用能力,在选项式中通过mixin
可以使用代码复用,但是使用它可能会导致数据来源不明确和命名冲突
。
因为不知道数据是从哪个mixin而来,不同mixin中函数名相同可能会产生意料之外的结果。例如我们要复用一个获取鼠标坐标的逻辑
js
// useMousePosition.js
import { ref, onMounted, onBeforeUnmount } from 'vue';
export function useMousePosition() {
const mouseX = ref(0);
const mouseY = ref(0);
function handleMouseMove(event) {
mouseX.value = event.clientX;
mouseY.value = event.clientY;
}
onMounted(() => {
window.addEventListener('mousemove', handleMouseMove);
});
onBeforeUnmount(() => {
window.removeEventListener('mousemove', handleMouseMove);
});
return {
mouseX,
mouseY,
};
}
js
<template>
<div>
<p>Mouse X: {{ mouseX }}</p>
<p>Mouse Y: {{ mouseY }}</p>
</div>
</template>
<script>
import { useMousePosition } from './useMousePosition';
export default {
setup() {
const { mouseX, mouseY } = useMousePosition();
return {
mouseX,
mouseY,
};
},
};
</script>
这样整个的数据流向就很清晰了,并且函数可以接受参数也就更加灵活。多个自定义钩子函数又可以相互组合,而不必担心混合对象的复杂性和顺序问题
补充:
RFC
它记录着Vue新功能或功能废弃的讨论,通过它你可以了解到某一功能新增或取消的前因后果。 RfC文档