前端宝典(自用)

188 阅读13分钟

一、HTML篇

1.h5新增了哪些东西

  canvas

  audiovideo

  localStroge、sessionStorage

  语义化标签:articleheaderfooter

2.doctype的作用

  位于html文件的第一行,告诉浏览器的解析器需要用什么文档标准来解析这个文档
  

3.重绘与回流(重排)

渲染树的节点发生了结构性变化,比如高度、宽度的更改,则会造成回流(重排)
渲染树的节点发生了非结构性变化,比如背景色的变化,则会造成重绘
回流一定会重绘,但是重绘不一定回流

二、css篇

1.css的盒子模型

border-box:ie怪异盒模型(内容包含paddingmargincontentcontent-box:W3C标准盒模型(内容只包含content

2.css中link和@import的区别

link属于html标签,没有兼容问题,页面在被加载时会同时加载link,权重高于@import
@import属于css提供的,只有在ie5以上才能识别,页面加载完毕后才会加载

3.css优先级

!important > 内联 > id > 类 > 标签

4.伪类和伪元素

伪类通常是给元素添加一些动态特性,比如hover active visit
伪元素通常是创建一些不在文档树中的内容,比如before、after

5.css单位

  px:绝对单位,精确像素
  em:相对单位,相对于父节点的font-size
  rem:相对单位,root em,相对于html节点的font-size
  vw:viewport width,视窗宽度
  vh:viewport height,视窗高度
  %:相对于父节点的宽高比
  

6.垂直居中的方式

   父相子绝,子divtopleft分别是50%,并且margin-topmargin-left分别是他宽度的一半的负值
   使用flex布局,通过justify-content或者align-items设置为center
   行高=高
   

7.css布局方式

table布局
flex布局
响应式布局
float布局

8.padding和margin的区别?

作用对象不同,padding作用于自身,margin作用于外部对象

9.如何让谷歌浏览器支持小字体

通过scale来实现,transform:scale(0.5

三、js篇

1.async和await

这两者属于组合使用,是用来让异步代码看上去像是同步代码在顺序执行,await用来等待一个异步方法的执行完成

2.数组的方法

改变原数组:
pop(从末尾删除一个元素,返回被删除元素)
push(从末尾添加一个或多个元素,返回新的长度)
shift(从开头删除一个元素,返回被删除元素)
unshift(从开头添加一个或多个元素,返回新的长度)
reverse(数组反转)
sort(将数组按一定逻辑排序)
splice(用于插入、删除或者替换元素)

不改变原数组:
concat(连接两个或多个数组)
every(检测数组的每个元素是否都满足条件)
some(检测数组中是否有元素满足条件)
filter(将数组中满足条件的元素过滤出来返回一个新的数组)
indexOf(搜索数组中的元素,返回他的索引)
join(将数组中元素按照一个标识组合拼成字符串)
toString(将数组转换成字符串)
lastIndexOf(返回数组中某元素最后一次出现的索引)
map(通过处理指定的数组,并返回新数组)
slice(选取数组中的一部分返回新的数组)
forEach(遍历数组)

3.原型链

每一个实例对象上都有个proto属性,他指向的是构造函数的原型对象,
构造函数的原型对象本身也是对象,所以就有proto属性,它又指
向了上一个原型对象,直到找到Object,这样一个一个向上找的过程就形成了原型链

4.闭包

   简单地说,就是在函数中返回函数,内部函数可以访问外部的变量,
   这个变量叫做自由变量
   为了保存变量,使其常驻内存,解决变量的私有化问题
   

5.es6的新特性

   增加了constlet
   增加了模板字符串(`${}`)
   增加了箭头函数
   引入了module的概念
   

6.js的数据类型有哪些

基础数据类型:
string(字符串) 
number(数字) 
null(不存在) 
boolean(布尔) 
undefined(未定义) 
symbol(独一无二,常用来解决键名重复问题) 
bigint(安全的大数字)

引用数据类型:
object(包含了function、array、date等)
基础类型在复制后是2个独立的东西,而引用类型则是
复制了一个指针,改变一个值也会影响另一个值

7.同源策略

是指协议名、域名、端口名都一致的情况下就是同源,
违背了同源策略,就会产生跨域,跨域的解决方式有
jsonp(只支持get请求)
cros(服务端在后台对接口的access-origin进行设置)
通过webpack自带的devServer(开发服务器)中进行proxy的设置

8.什么是promise

promise主要用于异步计算,可以将异步操作队列化,
按照期望的顺序执行
他有3种状态 pending(等待中)、fulfilled(成功)、rejected(失败),
他的状态改变,只会从pending变为后者两个,一旦变化,状态就凝固了

9.什么是递归

  递归就是指在函数内部调用自己
  优点是逻辑清晰,可读性强
  缺点是效率低,可能导致内存溢出
  

10.let和const的区别

两者都不允许重复定义对象,不存在变量提升,
const必须在定义时就赋值,let则可以只定义,他们都会形成封闭作用域
const定义的是常量,不允许修改,如果定义的是对象,则可以修改

11.存储方式

  cookie:在过期时间后自动删除
  localStorage:不会自动删除,存储持久数据,浏览器关闭后
  也不会清除除非手动清除
  sessionStorage:临时存储,关闭浏览器后自动删除

  cookie不能超过4K,后两者可以达到5M或更大

  cookie始终会参与到与服务器的通信中,通常放在header里面(即使不需要)
  

12.普通函数和箭头函数的区别

  箭头函数没有自己的this,他指向上一个作用域,普通函数指向的是自己
  
  改变this指向:
  call,apply(立即执行)
  bind(需要再手动调用一次)
  
  call,bind(传递的是参数列表)
  apply(传递的是数组)
  

13.typeof会返回哪些值

string 
number 
boolean 
undefined 
object 
function 
symbolnull也会被认为是object

14.预解析

在当前作用域中,js会将var声明的变量和function声明的变量进行提前声明,同时function还会提前被定义出来

15.事件冒泡和事件委托

冒泡:当一个事件发生后,会从该节点一直向上传播,触发每一层的父节点事件,
直至找到document对象(自下而上)。阻止冒泡则可以用e.stopPropagation(),
取消任务的默认行为可以用e.preventDefault()

委托:当需要给一个节点绑定事件时,该节点数量大或者是动态添加的,
不能确切的绑定至其上,或者为了节约内存,没必要给每个子节点都绑定事件,
此时可以将事件挂载在它的父节点上面,由父节点向下传播,实现子节点的触发(自上而下)

16.script 中 defer 和 async 的区别

script:会阻碍html的解析,直到该脚本下载完毕
async script:可能不会阻碍html的解析,使用异步的方式下载该脚本,下载完毕立即执行
defer script:完全不会阻碍html的解析,解析完毕后才开始下载脚本

17.事件循环

Event Loop:js处理事件时,同步任务是立即执行的,异步任务则又被分为宏任务和微任务,
微任务享有优先执行权,并且总会将当前的微任务全部执行完成后,再执行宏任务,
再执行完一条宏任务后如果发现有微任务则又立即执行微任务,若没有微任务则继续执行下一条宏任务

18.原型

原型:
每个对象都有_proto_属性,属于隐式原型,指向该对象的原型对象
每个函数都有prototype属性,属于显示原型属性

19.防抖节流

防抖:n秒后执行一次事件,如果重复触发,则重新计时(输入框搜索、下拉滑动加载)
const debounce = (fn, wait) => {
    let timeout = 0
    return () => {
        if (timeout) clearTimeout
        
        timeout = setTimeout(() => {fn()}, wait)
    }
}

节流:n秒内只执行一次事件,如果重复触发,则在下一个n秒后才会再次执行(只有timeout被清空后才继续执行下一次)
const throttle(fn, wait) => {
    let timeout = null
    return () => {
        if (!timeout) {
            timeout = setTimeout(() => {
                fn()
                timeout = null
            }, wait)
        }
    }
}

20.作用域链

一般来说,变量取值到创建这个变量的作用域中去取,如果拿不到,
就向上层作用域查找,直到查到全局作用域,这么一个查找的过程形成了一条作用域链

21.深拷贝与浅拷贝

浅拷贝:藕断丝连,改变一个会影响另一个
深拷贝:互不影响,两个独立的空间

四、vue篇

1.vue双向绑定的原理

在vue中,采用m(数据层)v(视图层)vm(数据视图交互层)的方式,
修改数据就能直接引起dom元素的变更,使用v-model指令来完成这一步

v-model的本质其实是v-bind和v-on的语法糖,其实是绑定了一个value的属性和input的事件

2.vue的生命周期有哪些

beforeCreate(创建前,this.$el为空,this.$data为空)
created(创建后,this.$el为空,this.$data有值)
beforeMount(在组件装载之前,,this.$el为空,this.$data有值)
mounted(组件装载完成,真实dom已经生成,,this.$el有值,this.$data有值)
beforeUpdate(在组件数据更新之前)
updated(组件数据更新后)
beforeDestory(组件销毁之前,一般用来做订阅的取消或者定时任务的清理)
destoryed(组件已经销毁)
activated(keep-alive的生命周期,组件在被激活时调用)
deactivated(keep-alive的专属,组件在被销毁时调用)

3.http请求应该在哪个生命周期完成

从理论上讲,不管放在created或者mounted中请求都是可以的
因为http请求是异步任务,从js的事件轮询机制可以知道,异步任务是会慢于同步任务执行的
所以不管放在哪个周期中,都是会等这两个周期内部的同步任务执行完成后才会执行http请求

但是,如果是有依赖关系的父子组件的情况下,如果写在created中,则先是父组件的http请求先完成,
子组件的http请求后完成
如果写在mounted中,则是子组件的http请求先完成,父组件的http请求后完成

4.v-if和v-show的区别

  v-if是直接将dom元素从页面上删除,再次切换需要重新渲染
  v-show是通过改变display属性来控制显隐的,切换不需要进行重新渲染
  一般来说,如果需要频繁的切换,建议使用v-show
  

5.v-for为什么需要绑定key

这是底层的diff算法需要的,给dom元素上绑定唯一的key值,可以很快的实现比较,加快页面的渲染,避免重复渲染

6.vue中的data为什么是一个函数而不是一个对象呢

因为js中对象是引用类型,组件是可以复用的,如果将data作为对象,
那么修改一个实例中的data值将会引起其他实例中的data值也发生变化

7.vue的单向数据流

数据从父组件传到子组件,子组件对这些数据没有修改的权限,只能请求父组件中的方法进行更改,
直接修改控制台会报错

8.slot

插槽的作用就好比是一段占位符,当复用组件时,使用对应的插槽名称,就可以替换掉占位内容,对复用和扩展组件有很大的好处
插槽分为匿名插槽,具名插槽和作用域插槽(可以传值)

9.keep-alive的作用

用来缓存组件,在组件的切换过程中执行的不是销毁操作而是将其保存在内存中,
他有自己的生命周期,activated(组件被激活)、deactivated(因为使用了keep-alive就不存在销毁操作了,
所以原本的销毁相关的生命周期无法使用,则使用这个进行代替)

首次进入被缓存组件,执行beforeCreate、created、beforeMount、mounted、**activated**
后面再进入则只执行activated

10.vue的diff算法(遍历循环比较)

1.vue在进行diff时,会调用patch函数,一边比对一边给真实的dom打补丁
2.vue在同级同层比较时,如果节点名称相同但是class不同,则会认为是不同类型的元素,就会删除重新创建
3.vue在比较时,会在旧集合和新集合两端同时存在指针,在比对时指针不断向中间移动

11.props

props: ['id'], 表示只是接收id这个参数
props: {
    id: Number,
    default: 0
}, 表示可以对id这个参数进行一些限制

12.父子之间通信方式

父使用子的方法:this.$refs.children.xxx()
子使用父的方法:this.$emit('xxx', params)
子组件使用.sync
<child :data.sync="xxx" />
$parent/$children/props/eventbus/provide,inject/$attrs,$listeners

13.vue的data必须是函数

vue之间的组件是可以复用的,如果data是对象的形式,则引用的是同一个地址,
无法将data独立出来,修改一个组件的data值其他组件里面的data也会受影响

14.methods、computed、watch

methods没有监听依赖的效果,每次访问methods将会重新计算

computed具有监听依赖的效果,每次访问computed只有依赖项发生了变化才会
重新求值,起到了一个缓存的作用。另外它可以监听多个依赖,适用于依赖多个值并进行一段计算的场景

watch则是用来监听单个依赖值的,可以拿到上一次的数据和新的数据,
适用于监听单个值变化后执行一段业务逻辑的场景

15.过滤器

常见写法:{{ message | filter(arg1, arg2) }}
变量与过滤器之间用大竖号分割开,将过滤器的方法写到filters里面,如:
filters:{
    xxx:function(args) {
    }
}
接收的参数中,message将作为第一个参数,arg1为第二个,以此类推

16.ref

用来创建引用对象的方式,如果用在dom元素上,引用指向的就是dom元素,
如果用在组件上,指向的就是组件实例,可以使用this.$refs来进行获取

17.nextTick 的原理和用途

数据修改后的延迟回调,立刻获得新的dom。

18.性能优化

1.减少data的数据,每个data都会被watch,尽量拆成组件
2.在循环的dom元素或组件上加入唯一的key标识,帮助快速完成diff算法
3.使用keep-alive缓存组件
4.第三方模块按需引入
5.v-for和v-if不能同时在一个节点或组件上使用

19. routeroute和router的区别

总的来说,$router是用来操作路由的,它是全局的router实例,里面有很多方法
$route则是用来获取路由信息的,比如query、params等,它是当前激活的路由对象

20.动态路由匹配

形如/user/:id这样的路径,叫做动态路由匹配,类似于一些详情页面,可以通过route.params.id获取参数

21.权限路由动态添加

路由分为公共路由和权限路由,权限路由则需要在登录成功后的角色信息中进行比对,
符合权限的路由才可以添加到当前用户所呈现的页面中,通过router.addRoutes方法加入

22.路由懒加载

如果不使用懒加载的话,当我们的路由特别多的情况下,在打包环节将会导致打包的js体积异常庞大,
但其实没必要一次性完全打包进去,只需在访问那个路由的时候再进行加载即可

推荐的懒加载方式是es6的import方法:
component: () => import('/* webpackChunkName: 'xxx' */...路径')
如果多个路由指定了相同的webpackChunkName,他们将会打包成一个js,否则则一个路由打包成一个js

23.v-for和v-if为什么不建议同时使用

因为v-for的优先级是高于v-if的,如果同时存在的话,就相当于在每个遍历中去进行if判断,
一旦当被遍历的数组很庞大时,性能开销也会很大,得不偿失。可以使用computed来完成

24.vue-router hash和history

hash:在浏览器地址栏中会带有#,在#后面的值的变化,并不会引起浏览器的请求,
兼容性较好,刷新页面也会正常加载

history:借助于H5新的api:pushState和replaceState来实现改变url而不发出浏览器的请求,
地址看上去更加优雅,但是刷新页面会返回404,需要在后端将页面配置到首页

25.响应式原理

本质是采用数据劫持结合发布订阅的方式,结合Object.defineProperty拿到每个属性的gettersetter,
在数据变动时发布消息给监听者,触发相应的监听逻辑

26.eventbus

建立bus文件
import Vue from 'vue'
export default new Vue()

发送消息
import bus from './bus'
bus.$emit('share', xxxx)

接受消息
bus.$on('share', xxxx)

移除消息
bus.$off('share')

27.http请求可以在beforeCreate完成吗

如果该请求没有封装成方法写到methods中,那么是可以的,
因为在beforeCreate中是无法访问到methods中的方法的,此时的dom及数据都尚未准备好

28.父子之间生命周期的加载顺序

父优先创建,子优先挂载

父:beforeCreate created beforeMount
子:beforeCreate created beforeMount mounted
多个子的话同上
父:mounted

五、vuex篇

1.vuex的五大核心

state: 全局数据管理
getters:类似于computed,返回对state处理后的结果
actions:提交mutations,可以写异步操作
mutations:修改state的值,只能写同步操作(若写异步则无法准确追踪state的变更)
modules:模块化管理,将state中的数据进行分类
modules中若有相同的actions则均会触发,解决方案是在每个module中声明namespaced: true这个属性
然后再调用的时候使用module的文件名称+action名称(user(module名称)/setToken(action名称))

2.为什么不能在actions中修改state

在未开启严格模式下,是可以修改的,但是不提倡
但是严格模式下控制台会抛出异常并且actions是异步的,不利于调试
vuex-persistedstate也只会读取mutations中state的变化而重写存储key

严格模式开启:
new Vuex.Store({ xxx, strict: true })

3.vuex的语法糖

state:
1. this.$store.state.xx
2. mapState({xxx}) (写在computed中)

getters:
1. this.$store.getters.xx
2. mapGetters({xxx}) (写在computed中)

actions:
1. this.$store.dispatch(xx)
2. mapActions([xx]) (写在methods中)

mutations:
1. this.$store.commit(xx)
2. mapMutations([xx]) (写在methods中)

一、基础核心考点(必问)

1. Vue2 和 Vue3 的核心差异有哪些?

核心答案

  • 底层架构:Vue3 基于 Proxy 实现响应式(替代 Vue2 的 Object.defineProperty),支持数组 / 对象动态新增属性、嵌套对象监听,无兼容性问题;
  • 组合式 API:Vue3 的setup/ref/reactive替代 Vue2 的 Options API,解决大型组件逻辑复用难题;
  • 体积优化:Tree-shaking 支持,按需引入核心 API(如computed/watch),打包体积更小;
  • 模板语法:支持多根节点、Teleport(传送门)、Suspense(异步加载);
  • 生命周期:Vue3 重命名(如beforeDestroybeforeUnmount),组合式 API 中通过onMounted等钩子调用;
  • 性能:编译优化(PatchFlag 补丁标记、静态提升),减少虚拟 DOM 对比开销。

考察要点:响应式原理、性能优化、API 设计思想。

2. ref 和 reactive 的区别?各自的使用场景?

核心答案

表格

特性refreactive
数据类型支持基本类型(Number/String)+ 对象 / 数组仅支持对象 / 数组(不支持基本类型)
访问方式需要.value(模板中自动解包)直接访问,无需.value
响应式原理对基本类型包装为 RefImpl 对象,对对象内部调用 reactive基于 Proxy 递归代理对象
解构特性解构后仍保持响应式(需用 toRefs)直接解构会丢失响应式

使用场景

  • ref:单个基本类型数据(如countname)、需要单独监听的对象属性;
  • reactive:复杂对象 / 数组(如userInfolistData),适合批量管理相关状态。

考察要点:响应式实现、实际开发中的数据管理习惯。

3. setup 函数的执行时机?参数有哪些?

核心答案

  • 执行时机:在beforeCreate之前执行,此时组件实例尚未创建(thisundefined);

  • 核心参数:

    • props:组件接收的属性,是响应式的,不可解构(否则丢失响应式);
    • context:上下文对象,包含attrs(非 props 属性)、slots(插槽)、emit(触发父组件事件)、expose(暴露组件内部方法)。

扩展:Vue3.3 + 支持<script setup>语法糖,替代手写setup函数,自动编译、无需 return 变量,是当前主流写法。

二、进阶核心考点(中高级面试)

1. Vue3 的响应式原理?Proxy 相比 Object.defineProperty 的优势?

核心答案

  • Vue3 响应式核心流程:

    1. 通过reactive/ref将目标数据转为 Proxy 代理对象;
    2. 数据读取时(get拦截),收集依赖(track函数,记录当前组件的 effect);
    3. 数据修改时(set/deleteProperty拦截),触发依赖更新(trigger函数,执行 effect);
    4. 依赖收集容器:targetMap(WeakMap,key 为目标对象,value 为 depsMap)→ depsMap(Map,key 为属性名,value 为 effect 集合)→ effectSet(Set,存储关联的 effect)。
  • Proxy 的优势:

    1. 支持监听数组索引修改、数组方法(push/pop)、对象动态新增 / 删除属性;
    2. 无需递归遍历对象(Proxy 默认递归代理,Vue2 需手动递归);
    3. 支持 Map/Set 等集合类型,Vue2 无法监听;
    4. 拦截粒度更细(可拦截has/ownKeys等操作)。

考察要点:源码理解、响应式核心逻辑。

2. 组合式 API 中如何实现逻辑复用?(对比 Vue2 的 mixin)

核心答案:Vue3 推荐通过组合式函数(Composables) 实现逻辑复用,替代 Vue2 的 mixin:

  • 组合式函数:封装独立逻辑的函数(如useUseruseRequest),返回需要暴露的状态 / 方法;

  • 示例(封装请求逻辑):

    vue

    <script setup>
    import { ref } from 'vue'
    // 组合式函数:复用请求逻辑
    function useRequest(url) {
      const data = ref(null)
      const loading = ref(false)
      const fetchData = async () => {
        loading.value = true
        const res = await fetch(url)
        data.value = await res.json()
        loading.value = false
      }
      return { data, loading, fetchData }
    }
    
    // 组件中使用
    const { data, loading, fetchData } = useRequest('/api/user')
    fetchData()
    </script>
    
  • 对比 mixin 的优势:

    1. 逻辑来源清晰(明确知道哪个函数提供的状态),解决 mixin 的 “命名冲突”“逻辑溯源难” 问题;
    2. 按需引入,只复用需要的状态 / 方法,避免 mixin 的冗余逻辑;
    3. 类型友好,TS 支持更完善。

考察要点:逻辑复用方案、工程化思维。

3. Vue3 的性能优化手段有哪些?(编译 + 运行时)

核心答案

  • 编译阶段优化:

    1. PatchFlag(补丁标记):编译时给动态节点打标记(如 TEXT、CLASS),运行时只对比带标记的节点,减少虚拟 DOM 对比;
    2. 静态提升:将静态节点(无动态绑定)提升到渲染函数外,避免每次渲染重新创建;
    3. 缓存事件处理函数:@click="handleClick"编译为缓存函数,避免每次渲染重新生成;
  • 运行时优化:

    1. 响应式优化:使用shallowRef/shallowReactive避免深层代理(如纯展示的大数据对象);
    2. 计算属性缓存:computed默认缓存,避免重复计算;
    3. 虚拟列表:使用vue-virtual-scroller处理长列表,只渲染可视区域;
    4. 组件懒加载:defineAsyncComponent+ 路由懒加载(import());
  • 构建优化:

    1. Vite 替代 Webpack,利用 ESModule 按需编译,冷启动更快;
    2. Tree-shaking:Vue3 核心 API 支持按需引入,打包体积更小。

三、实战 & 生态考点(2026 趋势)

1. <script setup>的新特性(Vue3.3+)?

核心答案:2026 年主流使用 Vue3.4+,<script setup>的核心新特性:

  • defineProps/defineEmits支持泛型:无需手动导入,直接定义类型(TS 友好);

    vue

    <script setup lang="ts">
    // 泛型定义Props类型
    const props = defineProps<{
      list: Array<{ id: number; name: string }>
    }>()
    // 泛型定义Emits类型
    const emit = defineEmits<{
      (e: 'change', id: number): void
    }>()
    </script>
    
  • defineOptions:直接在<script setup>中定义组件选项(如nameinheritAttrs);

  • defineModel:简化 v-model 双向绑定(替代手动声明 props+emit);

  • 顶层 await:支持在<script setup>中直接使用 await,组件自动变为 Suspense 的异步组件。

2. Vue3 + Vite 项目的性能优化技巧?

核心答案

  • 依赖预构建:Vite 默认预构建第三方依赖(如 vue、axios),避免多请求,可通过optimizeDeps配置;
  • 按需引入:使用unplugin-auto-import自动导入 Vue API(如 ref、onMounted),unplugin-vue-components自动导入组件;
  • 构建优化:vite build时配置build.chunkSizeWarningLimit拆分大 chunk,build.cssCodeSplit拆分 CSS;
  • 图片优化:使用vite-plugin-imagemin压缩图片,build.assetsInlineLimit内联小资源;
  • 开发优化:配置server.fs.strict避免路径问题,optimizeDeps.include预构建慢的依赖。
3. Vue3 中如何实现 SSR?(2026 高频)

核心答案

  • 主流方案:Vue3 + Vite + Nuxt3(开箱即用的 SSR 框架);

  • 核心逻辑:

    1. 服务端:将组件渲染为 HTML 字符串(renderToString),返回给客户端;
    2. 客户端:激活(hydrate)静态 HTML 为可交互的 Vue 实例;
  • 优势:首屏加载快、SEO 友好;

  • 注意点:

    • 避免在服务端渲染阶段使用浏览器 API(如 window、document),可通过process.client判断环境;
    • 异步数据获取:使用 Nuxt3 的useAsyncData/useFetch,或手动在serverPrefetch中获取;
    • 缓存:对静态页面做 SSR 缓存,减少服务端渲染开销。

四、源码 & 原理考点(高级面试)

1. Vue3 的虚拟 DOM 优化(PatchFlag)原理?

核心答案

  • 问题:Vue2 的虚拟 DOM 对比是全量对比,即使只有一个文本节点变化,也会遍历整个 VNode 树;

  • 解决方案:Vue3 编译时给动态节点添加PatchFlag(补丁标记) ,运行时只对比带标记的节点:

    1. 编译阶段:分析模板,给动态绑定的节点打标记(如 TEXT=1、CLASS=2、STYLE=4);
    2. 运行阶段:执行patch函数时,只处理带 PatchFlag 的节点,跳过静态节点;
  • 扩展:静态提升(hoistStatic)将静态节点提升到渲染函数外,避免每次渲染重新创建 VNode。

2. 为什么 Vue3 中需要 toRefs?它的实现原理?

核心答案

  • 问题:reactive定义的对象解构后会丢失响应式(因为解构后是普通值,而非 Proxy 代理);

  • 作用:toRefsreactive对象的每个属性转为ref,解构后仍保持响应式;

  • 实现原理(简化版):

    javascript

    运行

    function toRefs(obj) {
      const result = {}
      for (const key in obj) {
        // 为每个属性创建ref,关联原对象的属性
        result[key] = toRef(obj, key)
      }
      return result
    }
    function toRef(obj, key) {
      return {
        get value() {
          return obj[key]
        },
        set value(val) {
          obj[key] = val
        }
      }
    }
    

总结

2026 年 Vue3 面试的核心考察方向可归纳为 3 点:

  1. 基础核心:组合式 API(ref/reactive/setup)、响应式原理、Vue2→Vue3 的差异;
  2. 实战优化<script setup>新特性、Vite 整合、性能优化(编译 / 运行时)、SSR;
  3. 源码进阶:PatchFlag、toRefs、依赖收集 / 触发更新的核心逻辑。