源码优化

代码管理方式: monorepo

image.png
image.png
使用 monorepo 把各个功能模块拆分到不同的 package 中

  • compiler:模板编译的相关代码
  • core:与平台无关的通用运行时代码
  • platforms:平台专有代码
  • serve:服务端渲染的相关代码
  • sfc:vue 单文件解析相关代码
  • shared:共享工具代码

    Ts

    性能优化

    源码体积优化

  • 移除冷门 feature (filter、inline-template 等)

  • 引入 tree-shaking 的技术,减少打包体积

    tree-shaking 依赖 ES2015 模块语法的静态结构(即 import 和 export),通过编译阶段的静态分析,找到没有引入的模块并打上标记

  1. export function square(x) {
  2. return x * x
  3. }
  4. export function cube(x) {
  5. return x * x * x
  6. }
  1. import { cube } from './math.js'
  2. // do something with cube
  1. /* 1 */
  2. /***/ (function(module, __webpack_exports__, __webpack_require__) {
  3. 'use strict';
  4. /* unused harmony export square */
  5. /* harmony export (immutable) */ __webpack_exports__['a'] = cube;
  6. function square(x) {
  7. return x * x;
  8. }
  9. function cube(x) {
  10. return x * x * x;
  11. }
  12. });

未被引入的模块被标记,在压缩阶段会利用如 uglify-js、terser 等压缩工具删除无用代码

数据劫持优化

数据响应式:DOM 是数据的一种映射,数据发生变化后可以自动更新 DOM,用户只需要专注于数据的修改,没有其余的心智负担。 通过劫持数据的访问和更新来实现

image.png

  1. Object.defineProperty(data, 'a',{
  2. get(){
  3. // track
  4. },
  5. set(){
  6. // trigger
  7. }
  8. })

Vue2 中使用 Object.defineProperty 存在的缺陷:需要预先知道要拦截的 key 值,所以不能监测对象属性的添加和删除。(使用 $set 和 $delete 处理)

  1. export default {
  2. data: {
  3. a: {
  4. b: {
  5. c: {
  6. d: 1
  7. }
  8. }
  9. }
  10. }
  11. }

多层嵌套数据要劫持内部深层次的对象变化,需要递归遍历这个对象,执行 Object.defineProperty 把每一层对象数据变成响应式,所以有较大的性能负担

  1. observed = new Proxy(data, {
  2. get() {
  3. // track
  4. },
  5. set() {
  6. // trigger
  7. }
  8. })

Vue3 使用 Proxy 做数据劫持,能够劫持整个对象,相应的属性增删就能监测到
但 Proxy 也不能监听内部深层次的对象变化,Vue3 是在 getter 中去递归响应式,这样只有当使用到该数据时才会变成响应式,提高了性能

编译优化

image.png
image.png

Vue 能保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 vnode 树

  1. <template>
  2. <div id="content">
  3. <p class="text">static text</p>
  4. <p class="text">static text</p>
  5. <p class="text">{{message}}</p>
  6. <p class="text">static text</p>
  7. <p class="text">static text</p>
  8. </div>
  9. </template>

image.png
因为这段代码中只有一个动态节点,所以这里有很多 diff 和遍历其实都是不需要的,这就会导致 vnode 的性能跟模版大小正相关,跟动态节点的数量无关,当一些组件的整个模版内只有少量动态节点时,这些遍历都是性能的浪费。

Vue.js 3.0 通过编译阶段对静态模板的分析,编译生成了 Block tree。Block tree 是一个将模版基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个 Array 来追踪自身包含的动态节点。借助 Block tree,Vue.js 将 vnode 更新性能由与模版整体大小相关提升为与动态内容的数量相关。

语法 API 优化:Composition API

优化逻辑组织

将某个逻辑关注点相关的代码全都放在一个函数里

优化逻辑复用

Vue2 中使用 mixins 复用逻辑,存在大量 mixins 时,会导致命名冲突和数据来源不清晰问题

  1. const mousePositionMixin = {
  2. data() {
  3. return {
  4. x: 0,
  5. y: 0
  6. }
  7. },
  8. mounted() {
  9. window.addEventListener('mousemove', this.update)
  10. },
  11. destroyed() {
  12. window.removeEventListener('mousemove', this.update)
  13. },
  14. methods: {
  15. update(e) {
  16. this.x = e.pageX
  17. this.y = e.pageY
  18. }
  19. }
  20. }
  21. export default mousePositionMixin
  1. <template>
  2. <div>
  3. Mouse position: x {{ x }} / y {{ y }}
  4. </div>
  5. </template>
  6. <script>
  7. import mousePositionMixin from './mouse'
  8. export default {
  9. mixins: [mousePositionMixin]
  10. }
  11. </script>

使用 hook 写法

  1. import { ref, onMounted, onUnmounted } from 'vue'
  2. export default function useMousePosition() {
  3. const x = ref(0)
  4. const y = ref(0)
  5. const update = e => {
  6. x.value = e.pageX
  7. y.value = e.pageY
  8. }
  9. onMounted(() => {
  10. window.addEventListener('mousemove', update)
  11. })
  12. onUnmounted(() => {
  13. window.removeEventListener('mousemove', update)
  14. })
  15. return { x, y }
  16. }
  1. <template>
  2. <div>
  3. Mouse position: x {{ x }} / y {{ y }}
  4. </div>
  5. </template>
  6. <script>
  7. import useMousePosition from './mouse'
  8. export default {
  9. setup() {
  10. const { x, y } = useMousePosition()
  11. return { x, y }
  12. }
  13. }
  14. </script>