介绍
vue面试题的一些个人理解
# vue面试题
# 1.说说对vue的理解(认识,优缺点)
面试官想问的就是对vue的了解程度
下面是我的理解
vue是一个构建数据驱动的web界面渐进式框架。vue包括核心渲染器,响应式系统,组件化系统。使用虚拟dom提升性能,vue包含编译时和运行时两个状态,在编译时可以通过编译优化区别于传统的diff算法,进行靶向更新,大大提升了性能。
# 2.响应式系统面试相关
# 1.响应式基本原理
响应式系统包含副作用函数和响应式数据
核心思想
当副作用函数内部使用了响应式数据时,在读取阶段把当前副作用函数保存起来,当响应式数据发生变化时,把保存的副作用函数取出来再执行一遍。
实现
在vue3中我们使用proxy对数据进行代理,当对数据进行读取时,调用track把当前副作用函数activeEffect保存到bucket中,当数据改变时会调用trigger触发变化。
全局变量activeEffect,用来保存当前正在执行effect
保存effect和数据之间映射的bucket,wekmap数据结构
分支切换问题
当前副作用函activeEffect会保存与该函数相关联的依赖集合,deps数组
在tarck执行过程中将deps保存进来,在每次执行副作用函数之前先将依赖中当前effct删除掉,执行完再添加
嵌套的effect
用副作用函数栈effectStack,在副作用函数执行时,将当前副作用函数压入栈,执行完后弹出栈,并始终让activeEffect指向栈顶的副作用函数,这样就能做到一个响应式数据只会收集当前直接读取其值的副作用函数。
避免无限执行
如果trigger执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行。
# 2.调度原理
可调度性是响应式系统非常重要的特性。指的是当trigger动作触发副作用函数重新执行时,有能力决定执行副作用函数的时机,次数以及方式。
effect副作用函数第二个参数,传递options对象里面可以指定用户传递的scheduler
然后把options挂载到对应的副作用函数上,在触发时候我们可以直接执行scheduler而不是直接执行effectFn
# 3.计算属性
通过effect Options对象指定lazy属性为true,在effect对副作用函数包装后不立即执行。
effect内部会包装一个副作用函数effectFn,而真正的副作用函数是effect的第一个参数,在effectFn中通过res保存该函数的执行结果并作为返回值。
effect会返回内部包装的副作用函数,这样结合lazy可以实现懒计算,然后再computed函数里设置变量dirty标识是否需要重新计算,设置变量value保存缓存结果。
返回一个具有getter的对象,当dirty为true是会重新执行副作用函数,并缓存结果,然后再设置dirty为false,为了解决外层effect嵌套计算属性问题,需要在这里手动调用trach进行数据跟踪。设置effect的scheduler方法,当其执行的时候,如果dirty不为true改为true,然后手动调用trigger触发响应
简单来说就是computed是一个函数,接受一个getter参数,内部设置dirty和value变量,通过effect指定lazy和scheduler实现懒计算和当依赖变更时更新dirty标识,表示需要重新计算和缓存数据。为了解决外层effect引用计算属性,需要在返回的对象getter中进行手动调用track追踪变化,在scheduler中调用trigger手动触发响应
# 4.watch实现原理
本质就是观测一个响应式数据,当数据变化时通知并执行相应的回调函数。
watch的第一个参数可以传响应式数据也可以传一个函数,传响应式数据会调用traverse进行递归读取建立联系。
通过lazy让函数懒执行,在scheduler中调用用户传递的回调函数,通过执行effect返回的函数拿到新值和上一次执行的旧值一起作为参数传递给回调函数。
立即执行选项,immediate把scheduler封装成一个方法,当传入immediate时先执行一遍。
在watch监听到变更后,在副作用函数执行之前,会调用通过onInvidated函数注册的过期回调。
# 5.代理对象
- 访问属性
- key in obj
- for in 循环遍历
- 通过get拦截属性读取
- 通过has拦截in操作符
- 通过ownKeys间接拦截for in遍历
当设置新属性或者删除属性时,都需要触发for in循环相关的副作用函数,即把那些与iterate_key相关的副作用函数也取出来执行。
# 6.浅响应和深响应
深响应就是递归调用reactive
# 7.代理数组
- 通过索引访问
- 访问数组长度
- for in 循环遍历
- for of迭代遍历数组
- 数组的原型方法
注意点:
如果设置的索引值大于数组当前的长度,那么更新数组的length属性,并且触发与length相关联的副作用函数
for of会读取数组的长度和每个值的索引
# 解决每次调用reactive都生成新的代理对象问题
定义reactiveMap,用来存储原始对象到代理对象的映射。
# 代理数组原型方法
通过定义一个arrayInstrumentations对象,在里面重写原型方法
当get拦截函数触发时,先判断target是不是数组,如果是且键值存在于arrayInstrumentations上,则reflect.get的第一个参数为arrayInstrumentations,返回定义在该对象上相应的值
# 3.pinia源码
pinia主要导出两个方法
createPinia和defineStore
createPinia主要是将当前对象注册到vue实例上
基于effectScope实现
创建effectScope时会把当前activeEffectScope作为parent,当effectScope停止时,其子effect也会停止。
createPinia主要实现原理
通过返回一个markRaw标记过的对象,该对象是vue插件形式,通过install方法的参数可以获取当前vue的app实例,通过setActivePinia把当前pinia对象保存到全局,然后通过app.provide把该pinia对象注册到vue实例上,其后代子孙组件可以通过inject获取到。
然后暴露use方法可以注册插件,然后初始化_s属性为map对象,为后面defineStore保存store
defineStore实现原理
defineStore接受两个参数,第一个参数可以是piniaOptions也可以是id,第二个参数是setup函数或者piniaOptions选项。
在defineStore函数内部返回一个函数useStore
useStore通过getCurrentInstance获取当前vue实例,如果实例存在表示是在组件内部使用的可以通过injcet方法取到pinia对象
通过pinia._s看当前id是否存在store,如果有则返回,没有就通过createSetupStore或者createOptionsStore创建store。
createSetupStore原理
传入id和setup函数和当前pinia对象
使用传入的pinia对象属性_e.run,该属性是一个独立的effect作用域,在run函数内再开启一个effectScope,返回scope.run(()=>setup())这样就能收集到setup函数中的所有副作用函数,并进行统一的管理。返回pinia.e.run返回setup函数执行的返回值
然后处理返回值setupStore,遍历该对象,如果属性值是函数表示其为actions进行wrapAction包装一下,方便后面$onAction监听action执行
最后添加一些特殊的方法如$patch,$dispose然后使用reactive变成响应式对象返回,并添加到pinia对象上保存起来。
createOptionsStore原理
就是对options参数进行处理,构造一个setup函数返回值是options里的内容,然后再传入createSetupStore
使用option会有一个重置方法$reset,内部其实是使用$patch进行重置