星纵物联面试总结

Vue3 对比 Vue2 在渲染方面做了什么性能优化

  • 静态提升

    在 Vue2 中,每次渲染时都会重新创建 VNode 节点,即使是静态节点也会被重新创建。这会导致一些不必要的性能损耗。而在 Vue3 中,引入了静态提升的概念,它会将静态节点在编译阶段提升为常量,避免了重复创建的开销。

    1
    2
    3
    4
    5
    6
    7
    <!-- 模板 -->
    <div>
    <h1>我是固定标题</h1>
    <!-- 静态 -->
    <p>{{ dynamicText }}</p>
    <!-- 动态 -->
    </div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 提升的静态节点(就像提前造好的零件)
    const _hoisted_1 = /*#__PURE__*/ _createVNode('h1', null, '我是固定标题')

    function render() {
    return (
    _openBlock(),
    _createBlock('div', null, [
    _hoisted_1, // 直接使用预制件
    _createVNode('p', null, ctx.dynamicText) // 动态创建
    ])
    )
    }

    除了静态节点以外,一些静态的属性,例如<div class="header"></div>中的 class 属性也会做静态提升。

  • 预字符串化

    静态提升的进阶版,当模板里有大段纯静态 HTML 时(比如一个包含几十个固定元素的导航栏),Vue3 会把这些内容提前转成字符串形式缓存起来。

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 原模板 -->
    <nav>
    <ul class="menu">
    <li><a href="/home">首页</a></li>
    <li><a href="/about">关于</a></li>
    <!-- 后面还有20个固定不变的菜单项 -->
    </ul>
    </nav>

    当编译时如果发现整个<nav>都是静态内容,就会直接把这段 HTML 转成类似 "<nav><ul class=...>" 的字符串,生成渲染函数时直接返回这个字符串,省去创建虚拟 DOM 的过程,相当于直接把预制好的 HTML 字符串 innerHTML,比逐个创建 VNode 快 3-5 倍。

    1
    2
    3
    4
    5
    // 普通静态提升(生成 VNode)
    const _hoisted_1 = /*#__PURE__*/_createVNode("nav", null, [...])

    // 预字符串化(直接字符串怼)
    const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<nav><ul class=\"menu\">...", 20)

    这个优化在 SSR 场景效果更明显,因为服务端渲染字符串拼接本就是强项,Vue3 会智能判断何时启用这个策略。

  • 缓存事件处理函数

    在 Vue2 中,每次渲染时都会重新创建事件处理函数,即使是相同的事件处理逻辑。这会导致一些不必要的性能损耗。而在 Vue3 中,引入了缓存事件处理函数的概念,它会将事件处理函数在编译阶段缓存起来,避免了重复创建的开销。

    下面是一个 Vue2 和 Vue3 的编译结果对比示例:

    1
    <button @click="count++">plus</button>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // vue2
    render(ctx){
    return createVNode("button", {
    onClick: function($event){
    ctx.count++;
    }
    })
    }

    // vue3
    render(ctx, _cache){
    return createVNode("button", {
    onClick: cache[0] || (cache[0] = ($event) => (ctx.count++))
    })
    }
  • Block Tree

    Block 相当于一个箱子,搜集了所有动态节点的特殊 vnode。它比普通的 vnode 节点多了dynamicChildren属性,用于存储内部所有动态子节点(例如带有v-forv-if/v-else-if/v-else的节点,或有中括号模板的节点)。当组件的数据发生变化时,Vue 3 只会遍历 Block Tree 中的动态部分,而不是整个模板,这样可以大大减少需要检查的节点数量,提高更新性能。

  • PatchFlag

    PatchFlag 主要用于对 vnode 做标记,标记这个 vnode 中的哪个部分会发生变化,例如文本变化,class 变化,style 变化等。这样当进行 diff 时,可以进行更精准的比较,如果只有文本变化时,就可以跳过 class,style 之类的比较。


在封装Vue组件时,会有哪些的性能优化方面的考虑

  • 精准控制组件更新:动态切换频率高的组件用 v-show(避免重复销毁/重建),需要彻底移除的组件用 v-if(减少 DOM 节点数)。
  • 计算属性缓存:避免在模板内直接执行复杂计算,使用 computed 缓存结果。
  • 事件监听销毁:在 beforeUnmount 生命周期中移除全局事件监听,避免内存泄漏。
  • 防抖/节流优化:对于高频触发的事件(如 scrollresize)使用防抖/节流。
  • Props 设计优化:避免传递大型对象,按需传递原子化数据;使用 Object.freeze 冻结不需要响应式的配置数据。
  • 高级渲染策略:虚拟滚动优化长列表;组件懒加载。

ES5 与 ES6 的对象继承

  • ES5

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 定义父类构造函数和原型方法
    function Parent(name) {
    this.name = name
    }
    Parent.prototype.sayName = function () {
    console.log(this.name)
    }
    // 子类继承父类
    function Child(name, age) {
    Parent.call(this, name) // 1. 调用父类构造函数(继承实例属性)
    this.age = age
    }
    Child.prototype = Object.create(Parent.prototype) // 2. 继承原型方法
    Child.prototype.constructor = Child // 3. 修复constructor指向

    Child.prototype.sayAge = function () {
    console.log(this.age)
    }

    const c = new Child('xm', 10)
    c.sayName() // 'xm'
    c.sayAge() // 10
  • ES6

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 定义父类
    class Parent {
    constructor(name) {
    this.name = name
    }
    sayName() {
    console.log(this.name)
    }
    }
    // 子类继承父类
    class Child extends Parent {
    constructor(name, age) {
    super(name) // 必须调用super()后才能使用this
    this.age = age
    }
    sayAge() {
    console.log(this.age)
    }
    }

    const c = new Child('xm', 10)
    c.sayName() // 'xm'
    c.sayAge() // 10

Webpack 与 Vite 的介绍与对比

  • Webpack

    一个打包工具,它通过入口文件递归分析模块依赖,将各类资源(JS/CSS/图片等)转换为浏览器可识别的静态资产。

    • 优点:成熟的生态系统、丰富的插件和 loader、对多种资源和特殊需求具有很高的灵活性。
    • 缺点:开发过程中需要每次修改后重新打包(虽然支持 HMR,但整体速度较慢),在大型项目中冷启动和热更新时间可能较长。
  • Vite

    Vite 是一种新兴的构建工具,利用浏览器对原生 ES 模块的支持,在开发模式下无需提前打包。它只在浏览器请求模块时进行即时编译,并使用 esbuild(Go 语言编写)预构建依赖,从而大幅提升启动和热更新速度。生产构建时,仍使用 Rollup 进行传统打包优化。

    • 优点:极快的启动速度和热模块替换(HMR),配置简单,开发体验非常流畅,适合中小型项目或原型开发。
    • 缺点:开发与生产环境的构建方式不同可能导致的开发和环境与线上环境有差异。

Webpack 中 Loader 与 Plugin 配置的区别

  • Loader:是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
  • Plugin:在 webpack 的整个编译和打包流程中发挥作用。插件可以在构建的各个生命周期(如启动、编译、输出等阶段)中注入自定义行为。

HTTP/1.1 版本浏览器默认并发量

  • 典型范围:现代浏览器通常限制 6~8 个并发请求/域名(具体数值因浏览器而异)
浏览器 默认并发数
Chrome/Firefox 6
Safari 6
Edge 6
IE11 8
  • 限制对象:针对同一域名下的所有请求(包括 HTML/CSS/JS/图片等)
  • 限制原因:HTTP/1.1 支持持久连接(keep-alive),允许一个 TCP 连接传输多个请求,但请求必须按顺序处理。

dependencies、devDependencies和peerDependencies区别

  • dependencies:项目运行时必须的依赖包。
  • devDependencies:仅在开发阶段需要的工具或库。一般来说,这些依赖包是用于构建、测试、调试等用途,比如 Babel、Webpack等。
  • peerDependencies:指定的是项目所依赖的其他包的版本号范围,不主动安装,使用者需要手动安装符合要求的外部依赖项,如果不符合时会抛出错误。

npm 包的版本号管理

  • 版本号格式规范

    版本号格式为:主版本号.次版本号.修订号,对应 MAJOR.MINOR.PATCH,还可以追加一些扩展标识(可选),如预发布标签(-alpha.0-beta.1-rc.2),或构建信息(+{hash}

  • 版本号升级规则

    • MAJOR:不兼容的 API 变更
    • MINOR:向下兼容的功能新增
    • PATCH:向下兼容的问题修复
  • 依赖版本范围控制

    符号 含义 示例范围 允许的版本
    ^ 允许次版本和修订号更新 ^1.2.3 >=1.2.3 <2.0.0
    ~ 仅允许修订号更新 ~1.2.3 >=1.2.3 <1.3.0
    >/>= 大于等于 >2.1.0 2.1.1+
    x/* 通配符 1.x1.* >=1.0.0 <2.0.0
  • 发布相关的npm命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    npm version patch  # 1.2.3 → 1.2.4
    npm version minor # 1.2.3 → 1.3.0
    npm version major # 1.2.3 → 2.0.0
    npm version prerelease --preid=beta # 1.2.3 → 1.2.4-beta.0
    npm version prerelease # 1.2.4-beta.0 → 1.2.4-beta.1

    npm publish # 发布到公共/私有仓库
    npm publish --tag beta # 发布后通过 npm install package@beta 安装
    npm unpublish <package>@<version> # 注意:24小时内可删除已发布版本