前段时间,面试经常性吃完挂面,甚至遇到很简单的八股,也会表达得有点卡壳。分析了下,还是深度不够,或者说没有做整理,导致面试时候坑坑洼洼得回答。面试官那管你,能给你八股 畅谈你还说只是停留表面,那不给你下场。(感谢你的投递,你虽然很优秀,但是还差一点)
这下总结出来,让我们一起来探讨一下Vue(主要Vue3为主),也欢迎 jym 能在评论区分享些其他相关面试题,共进
欢迎reading and discuss,如果有文中分析有问题地,欢迎指出,轻喷就好哈
keep-alive底层原理
keep-alive 是一个Vue内置组件,主要用于缓存组件实例,避免重复创建和销毁,提高性能。(*不会渲染出 DOM 元素,也不会出现在父组件链中)。它的底层实现主要依靠以下几个关键部分:
缓存机制:keep-alive 内部使用一个对象 cache 来存储缓存的组件实例,键是组件的唯一标识key,值就是对应组件的虚拟节点vnode。同时,使用一个数组keys 来维护这些实例缓存顺序
而每个缓存顺序通过 lru 算法进行维护,当缓存的组件数量超过max 属性指定的上限时,会优先移除最久未使用的组件实例。
另外,keep-alive 还会在组件声明周期中添加activated 和 deactivated 钩子,当组件被激活时触发activated,被缓存时触发deactivated,并且不会再触发mounted 和 unmounted 钩子。
最后,核心代码逻辑是在keep-alive 的render 函数里面,会获取默认插槽的第一个组件节点,再根据include 和exclude 属性判断是否需要缓存该组件。若需要缓存,就检查cache 中是否已有该组件实例,有则直接从缓存获取,没有则添加到缓存里。
keep-alive 内置组件,可以使组件在切换时保持状态,避免重新渲染。
可以通过 include 和 exclude 属性来指定需要缓存的组件。
可以缓存 、列表渲染、组件缓存等。
需要缓存的组件push 到include 数组中,不需要缓存的组件push 到exclude 数组中。
Vue router两种模式原理
hash 模式:
监听hashchange事件,根据hash值,更新路由
使用location.hash = '#' + path,更新hash值
history 模式:
监听popstate事件,根据state值,更新路由
使用history.pushState(state, title, url),更新state值
pushState,replaceState方法,都是改变state值,不会触发popstate事件
需要手动
只有用户点击浏览器的前进、后退按钮或者调用 history.back()、history.forward() 等方法时才会触发popstate事件
history 兼容,服务器也配置了history模式,但是用户刷新页面,会404,需要服务器配置重定向,将所有请求重定向到index.html
reactive 和 ref 响应式的区别
Vue3 中的 reactive 是基于 ES6 的 Proxy 代理实现的响应式系统。它专门用于处理引用类型数据(如对象或数组),通过拦截对象属性的访问和修改操作实现响应式。具体工作原理是:当我们访问对象属性时,会触发 Proxy 的 get 陷阱(trap),此时进行依赖收集,将当前正在执行的副作用函数(effect)与该属性关联起来;当我们修改对象属性时,会触发 Proxy 的 set 陷阱,系统会通知所有依赖于该属性的副作用函数重新执行,从而实现界面的自动更新。reactive 的局限性在于它只能处理对象类型,不能直接代理原始类型值。
ref 是 Vue3 提供的另一种响应式解决方案,它可以处理任何类型的数据,包括原始类型(如数字、字符串、布尔值等)和引用类型。当我们使用 ref 包装一个原始类型值时,它会创建一个带有 value 属性的 RefImpl 对象,并通过 Object.defineProperty 的 get/set 拦截器实现这个 value 属性的响应式。当我们使用 ref 包装一个引用类型值时,它会在内部调用 reactive 来处理这个值,然后将其包装在 RefImpl 对象的 value 属性中。这就是为什么使用 ref 时需要通过 .value 访问或修改值的原因。在模板中使用 ref 时,Vue 会自动解包,不需要手动添加 .value。
vue 响应式的实现原理
实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
看张图理解:
computed 和 watch 的区别
都是用于响应式数据变化
computed 是计算属性,会自动追踪自身内部使用的响应式数据,并且在组件初始化是执行一次,会返回一个值。并且值会缓存,只要当依赖的响应式数据变化时,会重新计算。
watch 是侦听器,用于监听指定数据源的变化,每次数据变化时执行回调函数,处理副作用或自定义逻辑。
watch 可以监听单个或多个数据源,可以使用immediate 和deep 选项控制回调函数的执行时机。
其实如果多个数据源,使用watchEffect 就可以了。
使用场景:
computed 过滤、
watch 数据请求、dom操作
watch 类似useEffect,useEffect 通过依赖数组,需要手动实现深度监听
模块化怎么设计?
在项目中,将每个页面、组件、工具函数、api请求等拆分成独立模块,使用es6 中import/export 语法进行模块化, 提高代码的可维护性和可复用性。
通过按功能拆分,明确模块接口,使用模块化机制实现自动管理依赖
es6,静态分析,支持tree shaking 优化。
vue3 生命周期
初始化阶段、更新阶段 和 销毁阶段:”挂载前,挂载后,更新前,更新后,卸载前,卸载后
onBeforeMount:挂载前调用,dom未生产。DOM 渲染之前执行一些逻辑,比如初始化数据或者设置一些状态,
onMounted:dom 挂载,执行如网络请求等。onMounted 是在组件挂载完成后触发的生命周期函数。它非常适合操作 DOM 或发起网络请求,因为此时 DOM 已经渲染完成。
onBeforeUpdate:更新前面dom,比如取消定时器等,防止内存泄漏
onUpdated 更新后
onBeforeUnmount 卸载前
onUnmounted 卸载后
vue3 组件通信
pinia:状态管理库,通过集中式状态管理实现组件之间通信。(pinia相比 vuex,更加轻量,不去区分mutations和actions,,而是统一管理actions)在store文件夹下创建store 管理状态,defineStore,引入store 并且使用方法或状态
pinia 实现持久化插件pinia-plugin-persistedstate 实现。store中配置persist:true 开启持久化
单项数据流,兄弟组件状态共享,数据状态由父组件控制,父组件通过props 传递子组件,子组件通过emit触发事件,父组件通过自定义事件更新状态
$emit 触发组件自定义事件,允许子组件向父组件传递数据。父组件通过v-on 绑定事件监听器到子组件自定义事件。vue将监听器存储在子组件_event 属性中。(_event是一个对象,键是事件名称,值是监听数组)子组件中,emit 触发事件vue从_event查找事件监听器数组,依次调用这些函数。内部基于发布-订阅模式,用_events存储时间监听器。
on注册事件监听器,on 注册事件监听器,on注册事件监听器,emit 触发事件。 defineEmite
跨组件provide inject 告诉其他inject
事件总线:event bus,通过一个全局事件中心实现通信。兄弟组件,通过发布emit和订阅on进行通信,vue3使用mitt 进行状态管理。
作者:ys指风不买醉
链接:https://juejin.cn