三日
1. 如何理解Vue的模板编译原理
Vue的模板编译实际就是将模板字符串通过解析、优化和代码生成等步骤转换为渲染函数的过程。这个过程中,AST扮演了非常重要的角色,它用树形结构描述了模板的内容和结构,是编译过程的核心数据结构;同事,优化步骤可以提高后续渲染的性能,减少不必要的计算和比较;最后生成渲染函数实可以根据Vue实例的数据生成虚拟DOM,并最终渲染。
- 模板解析(Parse):将模板字符串转换为抽象语法树(AST);
- 优化(Optimize):静态优化,标记无需更新的节点,提高后续渲染性能;
- 生成(Generate):将优化后的AST转换为JS渲染函数;
- 生成渲染函数(Render):将生成的JS字符串转换为可执行函数,可根据Vue数据生成虚拟DOM;
2. Vue的自定义指令
- 全局注册
javascript
Vue.directive('focus', {
// 当被绑定的元素挂载到 DOM 中时......
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
- 局部注册
javascript
directives: {
focus: {
// 当被绑定的元素挂载到 DOM 中时......
inserted: function (el) {
// 聚焦元素
el.focus()
}
}
}
自定义指令钩子
bind
:只调用一次,指令第一次绑定到元素时调用;inserted
:被绑元素插入父节点时调用;update
:所在组件的VNode更新时调用componentUpdated
:指令所在组件的VNode及其子VNode全部更新后调用;unbind
:只调用一次;
注意事项:
- 自定义指令:专注于操作DOM,而不是更改数据或者处理复杂逻辑;
- 当与Vue的响应式系统交互时,要小心使用
updated
钩子,可能会在一个元素的生命周期内多次调用; - 如果需要在组件卸载时清理DOM操作,可使用
unbind
3. 对Vue的diff算法理解
vue的diff算法,也被称为"虚拟DOM差异算法",是Vue实现高效DOM更新机制的核心部分。这个算法用于比较新旧两个虚拟DOM树,将差异应用到实际的DOM上,从而避免不必要的DOM操作,提高性能。
- 双端比较:同层节点比较;
- 深度优先遍历:先比较当前节点的子节点,再比较兄弟节点;
key
比较:key不同,则直接会创建一个新的DOM节点;- 优化策略:同层比较、先头后尾、复用策略等;
二日
概念30分
1. Vue2和Vue3的区别
- 响应式:vue2使用的是
Object.defineProperty()
,vue3使用的是proxy
; - vue3全部由ts重构,对ts支持更友好;
- 自定义渲染器
- composition API
- vue3可以存在多个根节点,vue2只能有一个
1.1 Vue3相比较Vue2的优势
- 性能更好
- 体积更小
- 更好的ts支持
- 更好的代码组织
- 更好的逻辑抽离
- 更多的新功能
1.2 Vue3 升级了哪些重要功能
- createAPP()
- emits 属性:在子组件中生命 emits options 父组件的绑定事件;
- 生命周期:使用 setup 整合 beforeCreate 和 created 钩子, destory 修改为 unmount;
- 多事件处理:在点击事件中写入多个处理函数,用逗号分割;
- Fragment:可以存放多个根节点
- 移除.sync
- 异步组件的写法:需要从Vue 引入
defineAsyncComponent
,使用这个函数包裹import()
引入异步组件; - 移除filter:双括号中 用 | 分割转换含义;
- Teleport:主要场景就是把组件的嵌套层级提高;
- Suspense:用来加载异步组件未成功时的一些loading,主要实现原理:具名插槽;
- Composition API
1.3 vue3的响应式原理
vue3的响应式原理:主要基于ES6的Proxy对象来实现的,通过代理data对象来拦截属性的读写操作,进而实现数据的响应性,提供了更强大和灵活的响应式能力。
- Proxy 对象代理:Proxy对象能够拦截目标对象的各种属性操作,实现对数据的监听;
- 响应性处理:通过Proxy代理的对象访问属性时,触发 getter,设置属性时,触发setter;
- 多层属性嵌套与动态属性监听:Vue3.x的响应式系统能够处理多层属性嵌套的情况,无需手动进行深度监听。同事,对于动态添加的属性,也能够自动进行监听;
- 数组监听:由于Proxy对象能够直接监听动态添加的属性,所以能够自动监听数组所有增删改的方法;
- 作为单独模块使用:Vue3.x的响应式系统被封装为一个独立的模块,可以单独使用或者与其他库继承,提供了更多的灵活性和可定制型;
- 核心函数与API:reactive、ref、toRefs、effect、computed、watch、watchEffect等;
- track/ trigger机制:Vue3.x使用了底层的track/ trigger机制来收集依赖和触发更新,当数据发生变化时,trigger函数会遍历所有收集到的依赖,并触发它们的更新;
- Reflect对象:内置对象,提供了一系列与 Proxy handlers 相对应的方法。Vue3.x在实现响应式系统时使用了Reflect对象来调用目标对象的方法,以确保操作的正确性。
2. 如何理解ref、toRef、toRefs
- ref:
- 生成值类型的响应式数据
- 可以用于模板和
reactive
- 可以用过
.value
修改值
- toRef:
- 针对一个响应式对象(
reactive
封装)的prop
- 创建一个
ref
,具有响应式 - 两者保持引用关系
- 针对一个响应式对象(
- toRefs:
- 将响应式对象(
reactive
封装)转换为普通对象 - 对象的每个
prop
都是对应的ref
(不然reactive
响应式直接解构会是去响应式) - 两者保持引用关系
- 将响应式对象(
2.1 为何ref需要value属性
- ref是一个对象(不丢失响应式,值类型不能用proxy代理),value储存之
- 通过
.value
属性的get
和set
实现响应式 - 用于模板、reactive时,不需要
.value
,其它情况都需要
3. 设计模式
- 工厂模式:传入参数即可创建实例(eg:虚拟DOM根据参数的不同返回基础标签 VNode 和组件 VNode );
- 单例模式:真个程序仅有一个实例(eg:
vuex
和vue-router
的插件注册方法install
,判断如果系统存在实例就直接返回掉); - 观察者模式:响应式数据原理;
- 策略模式:指对象的某个行为,在不同场景中有不同的实现方案(eg:选项的合并策略);
- 代理模式:proxy;
4. 虚拟DOM是什么?
虚拟DOM,即用js模拟一棵dom树,放在浏览器内存中,当需要变更时,虚拟dom使用diff算法进行新旧比较,将变更结果放在队列中,反应到实际的dom树上,减少了页面dom操作。
优点:
- 保证性能下限:虚拟DOM需要适配任何上层API可能产生的操作,所以它属于普适的,所以性能不是最优的;
- 无需手动操作DOM;
- 跨平台:虚拟DOM实际上是SJ对象,而DOM与平台强相关,因此,虚拟DOM可以更方便进行跨平台操作,比如服务器渲染,weex开发等;
缺点:
- 无法进行极致优化:首次渲染大量DOM时,由于多了一层计算,会比innerHTML插入慢。
5. mixin是什么?vue3用了什么取代了?
mixin是多组件之间的相同代码逻辑的抽离混入。分为局部混入 和全局混入,提供了分发vue组件中的可复用功能。一个mixin对象可以包含任何组件选项。
缺点
- 来源不明确;
- 多mixin可能会造成命名冲突;
- 迷信和组件可能出现多对的多的关系,复杂度比较高;
Vue3使用了 composition API 替代了mixin 的使用
- 代码提取;
- 代码复用;
- 命名冲突解决
6. vue-loader的用途
vue-loader 是 vue 文件的加载器,将 template、js、style转换为js模块。使用该插件,js可以写成es6,style样式可以支持多预防,template可以加 jade 等。
7. computed 和 watch 的使用
- computed:计算属性,存在数据缓存,能够优化一定的计算性能,内部实际是一个函数,根据需要做回调计算返回计算结果;
- watch:用于观察和响应数据变化的特性。无缓存,支持深度监听。异步或者开销较大的操作,可以适当使用watch。
8. 单页面应用和多页面应用
- 单页面应用:只有一个主页面,浏览器初始化需要加载所有必须的js、css等文件。用户体验好,内容改变不需要重加载整个页面;
- 多页面应用:指一个应用多个页面,页面跳转即整页刷新。不利于SEO,导航不可用,初次加载耗时多,页面复杂度高。
9. Vue的单项数据流
vue的单项数据流主要是指父组件通过props传递数据给子组件,但是子组件不能直接修改该数据的特性。需要修改则需要
$emits
上告。该规则主要是维护数据的可追踪和可维护性,使组件状态清晰。
10. vue-router 的路由钩子函数是什么?调用顺序是什么?
路由钩子函数:导航守卫。允许在路由发生变化前后处理权限验证、数据预加载等;
10.1 分类:
- 全局守卫:
beforeEach
:全局前置守卫,在路由跳转前触发;beforeResolve
:全局解析守卫,在路由开始解析之前触发,在beforeEach
和组件内的beforeRouterEnter
之后;afterEach
:全局后置守卫,在路由跳转之后触发。
- 路由独享守卫:
beforeEnter
:进入路由前触发,只在某个路由下有效;
- 组件内的守卫:
beforeRouterEnter
:在渲染组件的对应路由被确认前被调用;beforeRouterUpdate
:在当前路由被改变,但是该组件仍然被复用时调用。例如带有动态参数的路由,组件的实例已经存在,对这个这一件调用;beforeRouterLeave
:导航离开该组件的对应路由时调用。
10.2 调用顺序
beforeEach
→ beforeEnter
→ beforeResolve
→ beforeRouterEnter
→ beforeRouterUpdate
→ afterEach
10.3 vue-router组件复用导致路由参数失效怎么办
解决办法:
- 监听路由变化;
- 使用
:key
属性阻止复用:会影响性能; - 动态路由匹配
- 使用query代替params:query会附加唉URL的查询字符串中
使用30分
1. transition标签的使用
<transition>
组件为元素和组件提供过渡效果,可自定义或者使用内置过渡效果。
使用:
name
属性:自定义过渡类名:xxx-enter-active
等;<transition>
包裹,使用 CSS 来定义过渡的样式,如.v-enter-active
、.v-leave-active
等类名。- 过渡模式(
mode
属性):out-in
表示离开-进入过渡;in-out
表示离开-进入过渡; duration
:过渡持续时间,ms为单位,:duration="{ enter: 500, leave: 800 }"
- JS 钩子:
@before-enter
等 - 列表过渡:
<transition-group>
,并为其指定tag
属性,默认span
,即渲染的根元素。
2. Vuex的使用
- 状态管理:集中式存储管理应用的所有组件的状态,轻松实现跨组件的状态共享;
- 可预测的状态变化:主要通过
mutation
实现,可追踪可预测; - 结构化和模块化;
- Vuex特别使用于单页应用开发中大型项目的状态管理;
一日
概念30分
1. Vue2的响应式原理
Vue2是采用数据劫持结合观察者(订阅-发布者)模式的方式,通过
Object.defineProperty()
来劫持各个属性的setter
、getter
、dep
,以及Watcher实现依赖收集和派发更新的过程。
- vue将
data
初始化为一个Observe,并对每个数据绑定setter
、getter
,data
中的每个key
,都有独立的dep
(依赖收集器); compile
解析模板指令,将模板中的变量替换为数据,初始化渲染页面视图,并将指令对应节点绑定update()
,向dep
添加监听;mount
时,实例化一个Watcher,将收集器的目标指向当前Watcher,待属性dep.notice()
通知,调用自身update()
,触发compile
的回调;- MVVM作为数据绑定的入口,整合Observer、Compile、Watcher,达到数据变化触发视图更新,视图交互变化触发数据Model变更的双向绑定效果。
1.1 $set 的原理
因为响应式数据,,我们给对象和数组本身都增加了
__ob__
属性,代表的是 Observer 实例。当给对象新增不存在的属性 首先会把新的属性进行响应式跟踪, 然后会触发对象
__ob__
的dep
收集到的 Watcher 去更新,当修改数组索引时我们调用数组本身的splice
方法去更新数组。
1.2 vue的data为什么必须是一个函数?
主要使用终于js的特性所导致的。在components中,data作为一个函数返回值的形式定义,使组件在复用过程中都是一个新的数据对象,相当于每个组件实例都有自己的私有数据空间。
1.3 vue如何监听到数据的变化
- 响应式原理:
- 在data中定义一个属性,vue会遍历这些属性并使用
Object.defineProperty()
将其转换为getter/setter,使其具有相应性质; - 当组件被创建时,vue会编译模板为虚拟DOM渲染函数,当访问数据属性时,则触发属性的getter;
- 当数据属性发生变化时,vue会触发更新流程,重新运行渲染函数。
- 在data中定义一个属性,vue会遍历这些属性并使用
- 如何监听数据的变化:
- data:响应变化;
- computed:依赖的数据缓存更新;
- watch:异步更新;
- vuex:mutations、store 常用更新处理。
- vue3中的响应式变化:
- vue3引入了proxy替代
Object.defineProperty()
,从而提供更好的响应式支持,包含数据和Map、Set等原始类型的深度响应性; - Vue3还引入了 Composition API,允许使用更函数式的方法来组织组件逻辑,包括使用
ref
和reactive API
来创建响应式数据。
- vue3引入了proxy替代
2. MVVM的理解
MVVM就是模型+视图+框架视图三者实现数据视图的监听、通知、更新的双向绑定的。
3. Vue的生命周期是如何实现的
8个生命周期,分别为4个阶段(创建、载入、更新、销毁)的前后。
vue的生命周期钩子是Vue框架内置的一组回调函数,是通过Vue内部实现和JS的原型继承机制来工作的。
new Vue()
实例化初始配置等;- 生命周期钩子函数注册;
- 不同阶段调用触发;
- 原型链和继承:Vue组件实际上就是Vue实例,通过Vue的组件系统来创建和管理、访问。
- 对于异步组件,加载完成后触发特定的钩子,动态组件在切换时会根据组件状态触发响应的钩子
3.1 Tips
created
阶段,vue实例数据对象data
可使用,$el
还未有;beforeMounte
阶段,data
和$el
可使用,但是还是虚拟DOM节点,data.message
还未替换;Destroyed
阶段,vue实例解除绑定,但是DOM结构依然存在哦。
3.2 父子组件生命周期执行顺序
- 加载渲染:父
beforeCreate
-> 父created
-> 父beforeMount
-> 子beforeCreate
-> 子created
-> 子beforeMount
-> 子mounted
-> 父mounted
- 更新:父
beforeUpdate
-> 子beforeUpdate
-> 子updated
-> 父updated
- 销毁:父
beforeDestroy
-> 子beforeDestroy
-> 子destroyed
-> 父destroyed
3.3 Vue3生命周期
- Options API
beforeDestory
改为beforeUnmount
;destory
改为unmounted
;- 其它沿用Vue2 生命周期;
- Composition API
setup 相当于整合了beforeCreate 和
created`。其它生命周期分别是卸载 setup中的函数 onBeforeMount()
onMounted()
onBeforeUpdate()
onUpdated()
onBeforeUnmount()
onUnmounted()
** 使用**
- 不建议共有,会引起混乱;
- 小型项目、业务逻辑简单,用 Options API
- 中大型项目、逻辑复杂,用 Composition API
4. Vue组件封装
Vue.extend()
:全局注册;Vue.component()
:局部注册;
5. Vue组件通讯
props
和$emit
:- 自定义组件通讯:
$on
、$off
、$emit
$refs
$parent
和$child
- vuex
provide
和inject
:常用于组件库编写
5.1 $on
和 $emit
的本质
- 基于发布订阅模式;
$on
用于收集事件依赖;$emit
用于触发事件,根据传入的event在vm_events找到对应的事件,并执行invokewithErrorHandling()
(通过handler.apply(context, args)
和handler.call(context)
的形式执行对应的方法);
5.2 事件绑定原理
$on
和$emit
是基于发布订阅模式的,维护一个事件中心。$on
的时候将事件按名称存在事件中心里,称之为订阅者,然后$emit
将对应的事件进行发布,去执行事件中心里的对应的监听器
6. $nextTick是什么
$nextTick中的回调是下次DOM更新循环结束之后执行的延迟回调。主要是采用微任务优先的方式调用异步方法去执行nextTick包装的方法。
7. 宏任务和微任务
宏任务和微任务都是异步任务的分类,在JS引擎中执行方式和优先级不同。
- 宏任务:主线程上执行的代码块,包括:主代码块、定时器、UI渲染、时间等,这些任务会按照他们在代码中的顺序被添加至宏任务队列,等待执行;
- 微任务:更小、更轻量级的任务,通常不会阻塞主线程的执行,包括:Promise的then和catch,async中的await、MutationObserve监听器等。当宏任务执行完毕后,js会立即执行所有已添加到微任务队列中的任务。
为什么优先解决微任务?
微任务具有更高的执行优先级。在JS事件循环中,每当一个宏任务执行完毕后,js会立即执行所有已添加到微任务队列中的任务,然后再去执行下一个宏任务。这样能确保微任务尽快得到处理,从而提高应用程序的响应速度和性能。
此外,有限解决微任务还能避免一些潜在问题。例如:宏任务中执行了异步操作(如定时器或者网络请求),并且该异步操作的回调函数被添加到了微任务队列。那么如果这个回调函数依赖于宏任务中的某些数据或者状态,那么优先解决微任务可以确保这些回调函数能够在正确的上下文执行。
总之,在编写代码时,需要注意避免在宏任务中进行耗时操作,以免影响其它任务的执行。
8. Vuex
全局状态管理系统,用于多个组件中的数据共享、缓存等(无法持久化,内部核心原理是创造一个全局实例new Vue()
)。
state
:应用状态的数据结构;getter
:允许组件从Store中获取数据;mutation
:唯一一个更改store中状态的方法,必须为同步函数;action
:用于提交mutation
,而不是直接变更状态,可以包含任意异步操作;module
:多模块的store;
9. keep-alive
开发中缓存组件使用keep-alive
(内置组件),使用时会缓存不活动的组件实例,使在组件切换过程中将状态保留;
原理
keep-alive
是一个通用组件,内部定义了一个map
,缓存创建过的组件实例,返回的渲染函数内部会查找是否存在。由于component
的is
属性是一个响应式数据,因此只要变化,则会执行render
。
使用
javascript
// max:限制缓存组件的最大数量
<keep-alive include='' exclude='' max=3></keep-alive>
结合属性 include
和 exclude
可以明确指定缓存哪些组件或排除缓存指定组件。vue3 中结合 vue-router
时变化较大,之前是 keep-alive
包裹 router-view
,现在需要反过来用 router-view
包裹 keep-alive
。
LRU(Least Recently Used) 算法
LRU,即最近最少使用的缓存机制。以关键字key
操作。缓存容量达到上限的时候,它应该在写入新数据之前删除最久未使用的数据值,提供新空间;
组件缓存更新
- beforeRouter:进入路由的时候都会执行;
- activated:在
keep-alive
缓存的组件被激活的时候执行(如果需要在每次进入页面获取最新的数据,需要在activated
钩子获取最新的数据,承担原来的created
钩子中获取数据的任务);
10. history和hash路由实现原理?区别是什么
location.hash
:- 实际就是URL中
#
后面的东西。特点:不会被包含在http请求中,不会重新加载页面; - 可以为hash的改变添加监听事件:
window.addEventListener('hashchange', func, false)
; - 每一次hash的改变,均会新增一个浏览器的访问历史记录;
- 功能使用:更新视图但不重新请求页面;
- 特点:兼容性好,不美观;
- 实际就是URL中
location.history
- 使用了HTML5 History Interface中新增的
pushState()
和replaceState()
方法。 - 功能:
back()
、forword()
、go()
以及对历史记录进行修改; - 功能使用:单页应用前端路由,更新视图但不重新请求页面;
- 特点:美观,但是刷新页面会出现404,需要后端进行配置
- 使用了HTML5 History Interface中新增的
使用30分
1. v-for和v-if可以混合使用吗
可以,但是不建议使用,v-for
的优先级会高于v-if
,即先渲染后判断,会增加不必要的性能消耗。更好的解决方案是使用computed
处理数据再使用。
2. v-key的使用
Vue在渲染和更新时会用diff算法做最大限度的减少动态元素操作,尽可能实现就地修改、复用相同类型元素。因此,key
的标记作用能够使判断更准确、更快速。