• 【前端知识之Vue3】一文搞懂Vue3到底做了哪些优化


    前言

    本系列主要整理前端面试中需要掌握的知识点。本节介绍Vue3相对于Vue2做了哪些优化。


    一、Vue2存在的问题

    • 随着功能的增长,复杂组件的代码变得越来越难以维护;
    • 缺少一种比较“干净”的在多个组件之间提取和复用逻辑的机制;
    • 类型推断不够友好;
    • bundle的时间过久。

    由于上述Vue2的问题,Vue3的优化明显更小更快更友好了

    二、Vue3的更小

    Vue3移除一些不常用的API,引入tree-shaking,可以将无用模块“剪辑”,仅打包需要的,使打包的整体体积变小了。简单来说,treeshaking就是找出使用的代码。
    Vue2中,无论使用什么功能,最终都会出现在生产代码中,主要原因是Vue实例在项目是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到。

    import Vue from 'vue'
     
    Vue.nextTick(() => {})
    
    • 1
    • 2
    • 3

    Vue3中引入tree shaking特性,将全局API进行分块,如果不使用某些功能,就不会出现在打包文件中。

    import { nextTick, observable } from 'vue'
     
    nextTick(() => {})
    
    • 1
    • 2
    • 3

    三、Vue3的更快

    在更快方面,Vue3主要做出的优化有:diff算法优化、静态提升、事件监听缓存、SSR优化。

    1. diff算法优化

    vue3在diff算法中相比于vue2增加了静态标记,作用是为会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较。比如下述例子:

    <template>
        <div id="content">
            <p class="text">静态文本p>
            <p class="text">静态文本p>
            <p class="text">{{ message }}p>
            <p class="text">静态文本p>
            ...
            <p class="text">静态文本p>
        div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里的组件内部只有一个动态节点,剩余一堆都是静态节点,所以这里很多diff和遍历其实都是不需要的,会造成性能浪费。此时会给{{message}}标签标记一个flag=1,其他的静态文本会标记flag=-1,代表永远都不会进行diff操作。这样比较的时候可以直接选择flag=1的节点进行比较。

    2. 静态提升

    Vue3对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用,这样就可以免去了重复的创建节点。
    下面举一个例子来展示一下静态提升的源码:

    <span>你好span>
    
    <div>{{ message }}div>
    
    • 1
    • 2
    • 3

    没做静态提升之前:

    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createBlock(_Fragment, null, [
        _createVNode("span", null, "你好"),
        _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
      ], 64 /* STABLE_FRAGMENT */))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    做了静态提升之后:

    const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createBlock(_Fragment, null, [
        _hoisted_1,
        _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
      ], 64 /* STABLE_FRAGMENT */))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个例子除了能看到静态内容被放置在render函数以外,还能看到静态内容被打上了-1的标志。这就对应了上述的diff操作。

    3. 事件监听缓存

    <div>
      <button @click = 'onClick'>点我button>
    div>
    
    • 1
    • 2
    • 3

    上述的例子中,默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没必要去追踪它的变化,想办法将它直接缓存起来复用就会提升性能。因此要打开事件监听缓存,这样静态标记就不存在了,这部分内容也就不会进行比较了。
    没开启事件监听缓存之前:

    export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createBlock("div", null, [
        _createVNode("button", { onClick: _ctx.onClick }, "点我", 8 /* PROPS */, ["onClick"])
                                                 // PROPS=1<<3,// 8 //动态属性,但不包含类名和样式
      ]))
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    开启事件监听缓存之后:

    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createBlock("div", null, [
        _createVNode("button", {
          onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
        }, "点我")
      ]))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. SSR优化

    当静态内容达到一定量级的时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会直接innerHtml,就不需要创建对象,然后根据对象渲染。

    import { mergeProps as _mergeProps } from "vue"
    import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "@vue/server-renderer"
    
    export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
      const _cssVars = { style: { color: _ctx.color }}
      _push(`${
        _ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
      }>
    你好...
    你好
    ${ _ssrInterpolate(_ctx.message) }
    `) }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    三、Vue3的更友好

    vue3的更友好体现在,兼顾vue2的options API的同时还推出了composition API,大大增加了代码的逻辑组织和代码的复用能力,同时优化了vue2中数据劫持,vue2中通过Object.defineProperty,这个API并不能检测对象属性的添加和删除,而Vue3通过proxy解决了这个问题。

    1. Composition API

    • Vue2中,代码是Options API风格的,也就是通过填充(option)data、methods、computed等属性来完成一个Vue组件。这种风格始得Vue容易上手,但是Options API不够灵活,组件间很难优雅的公用代码,并且Vue2的书写方式与JS的开发原则相悖,比如methods中的this竟然指向实例而不指向methods所在的对象。
    • 在Vue3中,舍弃了Options API,开始使用Composition API。组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)。
    • 两种方式的对比:

    在这里插入图片描述 在这里插入图片描述

    • Compositon API的例子
    <template>
      <button @click="increment">
        Count: {{ count }}
      button>
    template>
     
    <script>
    // Composition API 将组件属性暴露为函数,因此第一步是导入所需的函数
    import { ref, computed, onMounted } from 'vue'
     
    export default {
      setup() {
    // 使用 ref 函数声明了称为 count 的响应属性,对应于Vue2中的data函数
        const count = ref(0)
     
    // Vue2中需要在methods option中声明的函数,现在直接声明
        function increment() {
          count.value++
        }
     // 对应于Vue2中的mounted声明周期
        onMounted(() => console.log('component mounted!'))
     
        return {
          count,
          increment
        }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • Vue3的Composition API使得Vue3开发风格更接近原生JS,带给开发者更多的灵活性。

    2. proxy

    • Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象现有属性,并返回此对象。但是,如果存在深层的嵌套对象关系,需要深层次的进行监听,造成了性能的极大问题。
    • Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性,包括新增属性和删除属性,并且Proxy可以监听数组的变化。
    function reactive(obj) {
        if (typeof obj !== 'object' && obj != null) {
            return obj
        }
        // Proxy相当于在对象外层加拦截
        const observed = new Proxy(obj, {
            get(target, key, receiver) {
                const res = Reflect.get(target, key, receiver)
                console.log(`获取${key}:${res}`)
                return typeof(res) == "object" ? reactive(res) : res
            },
            set(target, key, value, receiver) {
                const res = Reflect.set(target, key, value, receiver)
                console.log(`设置${key}:${value}`)
                return res
            },
            deleteProperty(target, key) {
                const res = Reflect.deleteProperty(target, key)
                console.log(`删除${key}:${res}`)
                return res
            }
        })
        return observed
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的 。
    • 正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外set、delete方法)

    四、总结:Vue3对于Vue2有什么更新

    • Vue2在使用过程中,随着项目越来越复杂,项目代码维护起来也越来越困难,主要原因是Vue2使用的是Options API,这种方式把同一个功能的数据、方法、请求都分开写在data、methods等Options中。并且组件之间相同功能的复用比较也比较困难,同时响应式也没有那么灵活,因此,Vue3做出了如下的更新:
    • 用Composition API代理Options API,正如刚刚所说,Options API 将相同的功能的不同部分都分开写,不仅不利于阅读和维护,也和原生JS的思想相悖,缺乏灵活性,Vue3采用的Composition API按照功能将代码分割开,这样方便维护,也方便复用。
    • 采用Proxy代理Object.defineProperty,Vue2通过defineProperty的get、set和发布订阅来完成响应式,但是defineProperty的get、set并不能监控深层的对象与数组的变化,需要手动调用set来增加、删除响应式属性,还是造成了一些麻烦。Vue3采用Proxy监控整个对象,无论多深的属性都可以被监控到。
    • Vue3增加了tree shaking,能在打包的时候只打包用到的组件,可以让运行速度更快和打包文件更小。
    • Vue3还改变了虚拟DOM的diff策略,在Vue2中,diff策略不会区别节点是静态节点还是动态节点,而对比过多的静态节点会造成资源的浪费。因此Vue3给每一个节点都打上了标签,如果标签不为-1,则证明是动态节点,在比较的时候也只需要比较动态节点,使diff算法的效率更高。
    • Vue3还增加了静态提升和事件监听缓存,将不需要重复创建的节点和方法单独提出、进行缓存,避免重复创建和加载。
    • Vue3还做了SSR优化。如果增加的静态内容过多,就会直接使用innerHTML的方法插入,而不会一个一个的创建的节点。
  • 相关阅读:
    教程四 在Go中使用Energy创建跨平台GUI - 开发者工具
    拜托!佛系点,你只是给社区打工而已
    【51单片机】智能百叶窗项目
    都说Redux不支持非序列化数据 简单讲解非序列化数据概念 并举例说明
    【多线程】详解——模拟设计Timer(结尾附码源)
    搭建安全扩展
    学而优则“创”西电学子助力openGauss教学“破圈”,一举斩获金奖
    【710. 黑名单中的随机数】
    macOS - 安装使用 SQLite
    前端笔记(10) Vue3 Router 监听路由参数变化
  • 原文地址:https://blog.csdn.net/weixin_44337386/article/details/126240442