1、说一下Vue的响应式原理?(你是如何理解MVVM的)
回答版本一: 当vue组件被创建时,在生命周期的第一阶段,Vue使用Object.defineProperty()对data选项进行遍历劫持并添加get/set钩子;在生命周期第二阶段,指令第一次与声明式变量touch时,发生依赖收集,再调用当前组件的watcher第一次更新DOM,DOM视图就显示出来了。当声明式变量发生变化时,vue再次通知Watcher更新视图,这就是响应式(原理)
回答版本二: 当new Vue时,Vue源码中有一个Observer,会对data中所有的数据进行劫持,把data中的数据处理成响应式,每处理一个响应式数据,都有一个Dep与之对应。同时编译模板,编译模板时会找到小胡子语法和指令,处理小胡子语法和指令时,会创建出一个个的Watcher,这些Watcher会存储到对应的Dep中,这个过程叫依赖收集,同时调用对应的Updater,完成对应的页面的初始化。当数据发生变化时,在Observer的setter中会监听到,数据变化,就通知Dep中的Watcher更新视图,每个Watcher都绑定了更新视图的回调函数。
Vue官⽅其实有说明,Vue虽然并没有完全遵守MVVM的模型,但是整个设计是受到它的启发的。
2、说一下Vue的生命周期(展开说,不仅仅是那几个钩子)?
- 创建阶段:beforeCreate、created
- 挂载阶段:beforeMount、mounted
- 更新阶段:beforeUpdate、updated
- 销毁阶段:beforeDestroy、destroyed
- 与动态组件有关的两个特殊的钩:activated(激活)、deactivated(休眠)
- 与组件异常捕获有关的一个钩子:errorCaptured
1)在beforeCreate之前会声明methods中的方法和声明生命周期钩子函数,这个钩子函数我们用的并不多
2)在created之前会注入一些数据,初始化响应式系统,我们通常在这个钩子函数中调接口,获取路由参数等
3)在beforeMount之前会通过el
$meounttemplate找模板,会把模块变成render函数 调用render函数创建虚拟DOM,虚拟DOM转化成真实DOM,进行挂载,通常这个钩子函数我们也用不到4)在mounted时,已表示真实DOM已挂载完毕,我们在这个钩子中通常调接口,开定时器,DOM操作,建立websocket连接 实例化echarts实例等。
5)当数据变化时,会触发beforeUpdate钩子,这个钩子通常也用不到。
6)在updated之前,要生成新的虚拟DOM,新的虚拟DOM和老的虚拟DOM进行对比,会执行patch运算,diff算法,找到两个虚拟DOM的最小差异,找到后,进行异步更新,key的目的就是最快找到最小差异,这个钩子也不常用,但是有点类似于watch侦听器或类似于$nextTick(),我们不能在这个钩子中更新数据,会导致死循环
7)当我们手动调用$destory()或路由切换时,会调用beforeDestroy这个钩子函数,我们可以在这个钩子函数中清空定时器,解除事件绑定,清除缓存... 8)当组件销毁时,就会移除当前组件的watcher,DOM就无法再更新,移除所有子组件,移除事件监听器,响应式系统就会失效,组件就死亡了,这个钩子函数我们用的也不多。
3、谈一谈你对 Vue.nextTick() 的理解?有什么用?
数据变化,进行set操作,代码确实是同步的,但是set行为是异步的;set操作修改声明变量,触发re-render生成新的虚拟DOM,进一步执行diff运算,找到脏节点集合,交给Vue背后的更新队列去执行循环更新。在更新队列中每一个更新任务都是一个更新单元,nextTick表示下一个更新单元(更新周期)。
我们set操作data(更新DOM),你希望访问这个DOM的最新状态时,使用this.$nextTick(handler)。
4. 谈一谈Vue中组件通信?
1)、父子组件通信:父传子使用自定义属性(props),子传父使用自定义事件($emit())。
2)、状态提升:当兄弟组件之间需要共享数据时,我们通常的做法是把这个数据定义它们的共同的父组件中,再通过自定义属性实现数据共享。
3)、provide/inject:这是在组件树中,自上而下的一种数据通信方案,也就是说只能父级组件中向后代组件传递。需要注意的是,当provide提供动态数据(声明式变量)时,动态数据发生变化,后代组件们不会自动更新。这是为什么呢?你自己从生命周期流程的角度去思考。
4)、ref通信:ref是Vue内置的一个属性,每一个HTML元素或组件都有这个属性;ref作用在HTML元素上得到DOM实例,ref作用在组件上得到组件实例。使用ref访问组件实例,进一步可以访问组件中的数据和方法。(说明:ref是一种快速的DOM的访问方式,当然ref也可作用在组件上得到组件实例。这些ref得到的DOM实例或组件实例,使用this.$refs来访问它们。ref尽量少用,除非某些难搞的需求。)
5)、插槽通信:借助<slot>组件实现从子组件向父组件传递数据,借助this.slots访问父组件给的插槽实例;在父组件插槽中使用#default='scoped'访问子组件<slot>回传的数据。这种通信在组件库中、工作中,非常常见!)
6)、$parent/$children:借助children可以实现,在任一组件中访问组件树中的其它任意组件实例,可以做到在组件中随意穿梭。(children表示的是当前组件的子组件们。)
7)、$attrs/$listeners:借助listenrs可以访问父组件给的自定义事件。在某些场景下,listeners可以替代props/$emit()这种通用的通信方案。
8)、事件总线:借助于Vue内置的事件系统(emit/once)实现“订阅-发布”式的通信,这种通信方式是一种与组件层级无关的“一对多”的通信。(工作中很少用,一些特殊的Vue项目才用得到事件总线。)
9)、Vuex通信:这是Vue架构中终极的通信方案,也是Vue架构中用的最多的一种通信方案。
5、Vue中逻辑复用技巧有哪些?
组件:复用UI+逻辑
混入
自定义指令
渲染函数&JSX
插件
过滤器
V3中的自定义hook
6、你项目的鉴权怎么做的?你这个管理系统的权限怎么设计的?
我们之前的公司中小公司,做的项目,大概有30个左右的模块,所以权限这一块,就前端去处理的。我们处理的流程大致是这样的:前端登录换取token,在导航守卫中,实现权限设计,首先判断有没有token,没有token,直接跳到登录页面。有token会进一步判断vuex中有没有用户信息。如果没有用户信息,拿着token,调用接口获取用户信息,用户信息中保存了最重要的字段,就是角色,有了角色后,通过算法生成当前用户可访问的动态路由规则(算法大至是使用后端返回的角色和路由元信息中的角色进行对比,得到可以访问的动态路由规则),有了动态访问的路由规则,再通过addRoutes方法,把得到的动态访问的路由规则添加到路由系统。
7、简述Vuex的工作流程?
背官方的图
5大概念,4个map,3个原则。
5大概念:
创建store时要用的五个概念(state/getters/mutations/actions/modules)
- state: {} 用于定义可被组件共享数据,是具有响应式的;在组件中使用this.$store.state来访问它们。
- getters: {fn} 用于计算state,相当于Vue的计算属性,当state发生变化时getters方法自动自动重新计算;在组件中使用this.$store.getters来访问它们。
- mutations: {fn} 专门用于修改state的,所以mutations方法是这样fn(state,payload)定义的;mutations方法在actions中或组件中使用,使用$store.commit('mutations方法',payload)
- actions: {fn} 专门用于调接口的,所以actions方法是这样fn(store,payload)定义的;在组件中使用this.$store.dispatch('actions方法', payload
- modules: {子store} 是一个Vuex架构层面的概念,用于拆分子store。大家在拆分子store务必在子store中使用namespaced:true开启命名空间。
1)、4个map:
mapState/mapGetters,必须写在computed计算属性中,用于访问state/getters数据。映射进来后,就可以用this来访问这些数据了。
mapActions/mapMutations 必须写在methods选项中,用于访问mutations/actions方法。映射进来后,可以用this调用这些方法。
它们的语法是相同的:map*('命名空间', ['k1', 'k2'])2)、3个原则:
原则1:只要使用Vuex一定要拆分store,拆分store后在根store上不要再使用state/mutations/actions。
原则2:在子store务必开启命名空间namespaced:true。
原则3:在组件中尽可能不要使用$store,建议使用四个map*方法。
8、说说你对 SPA 单⻚⾯的理解,它的优缺点分别是什么?
SPA( single-page application )仅在 Web ⻚⾯初始化时加载相应的 HTML、JavaScript 和 CSS。⼀旦 ⻚⾯加载完成,SPA 不会因为⽤户的操作⽽进⾏⻚⾯的重新加载或跳转;取⽽代之的是利⽤路由机制实现 HTML 内容的变换,UI 与⽤户的交互,避免⻚⾯的重新加载。
优点: 1)⽤户体验好、快,内容的改变不需要重新加载整个⻚⾯,避免了不必要的跳转和重复渲染; 2)基于上⾯⼀点,SPA 相对对服务器压⼒⼩; 3)前后端职责分离,架构清晰,前端进⾏交互逻辑,后端负责数据处理;
缺点: 1)初次加载耗时多:为实现单⻚ Web 应⽤功能及显示效果,需要在加载⻚⾯的时候将 JavaScript、CSS 统⼀加载,部分⻚⾯按需加载; 2)前进后退路由管理:由于单⻚应⽤在⼀个⻚⾯中显示所有的内容,所以不能使⽤浏览器的前进后退功能,所有的⻚⾯切换需要⾃⼰建⽴堆栈管理; 3)SEO 难度较⼤:由于所有的内容都在⼀个⻚⾯中动态替换显示,所以在 SEO 上其有着天然的弱势。
9、v-show 与 v-if 有什么区别?
⽤法上的区别: 1)v-show是不⽀持template; 2)v-show不可以和v-else⼀起使⽤;
本质的区别: 1)v-show元素⽆论是否需要显示到浏览器上,它的DOM实际都是有存在的,只是通过CSS的display属性来进⾏切换; 2)v-if当条件为false时,其对应的原⽣压根不会被渲染到DOM中;
开发中如何进⾏选择呢? 1)如果我们的原⽣需要在显示和隐藏之间频繁的切换,那么使⽤v-show; 2)如果不会频繁的发⽣切换,那么使⽤v-if;
10、数组中的哪些⽅法会触发视图的更新?
Vue 将被侦听的数组的变更⽅法进⾏了包裹,所以它们也将会触发视图更新,这些被包裹过的⽅法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
11、Vue中v-for的key 有什么作⽤?
1)key属性主要⽤在Vue的虚拟DOM算法,在新旧nodes对⽐时辨识VNodes。
2)如果不使⽤key,Vue会使⽤⼀种最⼤限度减少动态元素并且尽可能的尝试就地修改/复⽤相同类型元素的算法
3)使⽤key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素。
key 是 VNode 的唯⼀标记,通过这个 key, diff 操作可以更准确、更快速的达到复⽤节点,更新视图的⽬的。复⽤节点就需要通过移动元素的位置来达到更新的⽬的。
12、computed和method有什么区别?
计算属性和⽅法:
- 都可以通过this来访问
- 都可以对⼀些数据进⾏处理和计算
- 对于包含响应式数据计算的逻辑,应该使⽤计算属性,因为计算属性是有缓存。
computed和method的区别
1)computed底层会缓存, 性能更⾼
2)计算属性会基于它们的依赖关系进⾏缓存;
3)在数据不发⽣变化时,计算属性是不需要重新计算的
4)但是如果依赖的数据发⽣变化,在使⽤时,计算属性依然会重新进⾏计算
13、什么是双向绑定?v-model的本质是什么?
双向绑定:
1)即当数据发⽣变化的时候,视图也就发⽣变化,当视图发⽣变化的时候,数据也会跟着同步变化
2)v-model 是语法糖,它负责监听⽤户在表单元素中的输⼊事件来更新数据表单元素使⽤v-model的本质:
1)v-bind绑定value属性的值
2)v-on绑定input事件监听到函数,函数会获取最新的值赋值到绑定的属性中
<input type="text" :value="message" @input="message = $event.target.value" />
14、data选项为什么是⼀个函数⽽不是对象?
JavaScript中的对象是引⽤类型的数据,当多个实例引⽤同⼀个对象时,只要⼀个实例对这个对象进⾏操作,其他实例中的数据也会发⽣变化。
⽽在Vue中,我们更多的是想要复⽤组件,那就需要每个组件都有⾃⼰的数据,这样组件之间才不会相互⼲扰。 所以组件的数据不能写成对象的形式,⽽是要写成函数的形式。数据以函数返回值的形式定义。
这样当我们每次复⽤组件的时候,就会返回⼀个新的data,也就是说每个组件都有⾃⼰的私有数据空间 ,它们各⾃维护⾃⼰的数据,不会⼲扰其他组件的正常运⾏。
15、谈谈你对 keep-alive 的了解?
keep-alive 是 Vue 内置的⼀个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
1)⼀般结合路由和动态组件⼀起使⽤,⽤于缓存组件。
2)提供 include 和 exclude 属性,两者都⽀持字符串或正则表达式。include 表示只有名称匹配的组件会被缓存。exclude 表示任何名称匹配的组件都不会被缓存。其中 exclude 的优先级⽐ include ⾼。
3)对应两个钩⼦函数 activated 和 deactivated 。当组件被激活时,触发钩⼦函数 activated。当组件被移除时,触发钩⼦函数 deactivated。
16、说说Vue插槽的作⽤和平时开发中的应⽤?
插槽的作⽤:
1)⽀持在⽗组件⾃定义⼦组件中的个内容
2)让⼦组件更具有通⽤性,不必限定死某个内容
17、⽗⼦组件的⽣命周期顺序
加载渲染过程: ⽗beforeCreate -> ⽗created -> ⽗beforeMount -> ⼦beforeCreate -> ⼦created -> ⼦beforeMount ->⼦mounted -> ⽗mounted (间记:子mouted再到父mouted)
⼦组件更新过程:⽗beforeUpdate -> ⼦beforeUpdate -> ⼦updated -> ⽗updated
⽗组件更新过程:⽗beforeUpdate -> ⽗updated
销毁过程:⽗beforeDestroy -> ⼦beforeDestroy -> ⼦destroyed -> ⽗destroyed
18、Composition API和之Options API有什么区别?
1)在逻辑组织和逻辑复⽤⽅⾯,Composition API是优于Options API。
2)Composition API⼏乎是函数,会有更好的类型推断,对于TS的⽀持更友好。
3)Composition API对 tree-shaking 友好,代码也更容易压缩。
4)Composition API中⻅不到this的使⽤,减少了this指向不明的情况。
5)Composition API⽤起来稍微复杂⼀点,⽽Options API就⾮常简单、易于使⽤。
19、ref和reactive有什么区别?开发中如何选择?
ref和reactive都是响应式的API,都可以⽤来定义响应式的数据。 ref可以包裹任意数据类型,reactive只能包裹复杂数据类型,⽐如对象、数组。
ref返回⼀个ref对象,在script中取值需要通过value属性,但是在模板中使⽤会进⾏解包不需要调⽤value。
reactive包裹的是复杂数据类型,直接取⾥⾯的属性即可。ref⼏乎可以应⽤在任何场景,⽽且包含reactive适合的场景
reactive的应⽤场景⽐较受限,第⼀:值⽐较固定,第⼆:值与值之间是有联系的。 开发中尽量选择ref
20、Composition API常⻅的⼏个函数与⽤法?
ref:包裹任意类型的值,将包裹的值加⼊响应式
reactive:包裹复杂类型的值,将包裹的值加⼊响应式
computed:把⼀些复杂逻辑⽤computed进⾏包裹,如同Options API中的计算属性⼀样,computed会⾃动收集相关依赖,当依赖发⽣变化时,会⾃动进⾏更新
⽣命周期:Vue3中想要在beforeCreate和created中做的事,直接在setup中做即可,Vue3的其他的⽣命周期函数都要在前⾯加⼀个on,然后需要在vue中主动引⼊
watch:
1)watch可以监听单个数据源,也可以监听多个数据源
2)watch是懒执⾏,第⼀次是不会执⾏的,除⾮你为其提供第三个参数中的immediate属性为true
3)watch只有等到监听的数据源发⽣了变化后,才会执⾏第⼆个参数(回调)
4)watch可以获取监听数据源的前后变化的值
5)侦听多个数据源的时候,第⼀个参数是数组类型watchEffect:
1)watchEffect会⾃动收集依赖,收集的依赖是第⼀个参数,也就是回调函数中有哪些东⻄是加⼊响应式的
2)如果这个值加⼊了响应式就会被收集起来,当被收集的值发⽣了变化,就会重新执⾏这个回调函数
3)watchEffect第⼀次执⾏是在DOM挂载前执⾏的,所以如果你想在第⼀次执⾏时拿到DOM元素
4)需要传⼊第⼆个参数,第⼆个参数是⼀个对象,让其flush属性的值为post即可toRefs: 将数据变成响应式
21、Vue3中的watch和watchEffect有什么区别?
watch和watchEffect都⽤⽤来侦听响应式数据的变化,watch可以侦听指定的源,默认第⼀次不会执⾏,watchEffect虽不能指定侦听的源,但是会⾃动收集依赖,并默认会先执⾏⼀次。
watch:
1)watch可以监听单个数据源,也可以监听多个数据源
2)watch是懒执⾏,第⼀次是不会执⾏的,除⾮你为其提供第三个参数中的immediate属性为true
3) watch只有等到监听的数据源发⽣了变化后,才会执⾏第⼆个参数(回调
4) watch可以获取监听数据源的前后变化的值
5) 侦听多个数据源的时候,第⼀个参数是数组类型watchEffect:
1)watchEffect会⾃动收集依赖,收集的依赖是第⼀个参数,也就是回调函数中有哪些东⻄是加⼊响应式的
2)如果这个值加⼊了响应式就会被收集起来,当被收集的值发⽣了变化,就会重新执⾏这个回调函数
3)watchEffect第⼀次执⾏是在DOM挂载前执⾏的,所以如果你想在第⼀次执⾏时拿到DOM元素
4)需要传⼊第⼆个参数,第⼆个参数是⼀个对象,让其flush属性的值为post即可
22、vue-router路由的两种模式
vue-router中默认使⽤的是hash模式:
1)hash模式,带#。如:http://localhost:8080/#/page。改变hash,浏览器本身不会有任何请求服务器动作。
2)history模式,不带#, 如:<http://localhost:8080/page ,路径没有#。基于HTML5的pushState、replaceState实现。hash:
1)有 # 号
2)能够兼容到IE8
3)实际的url之前使⽤哈希字符,这部分url不会发送到服务器,不需要在服务器层⾯上进⾏任何处理
4)刷新不会存在 404 问题
5)不需要服务器任何配置history:
1)没有 # 号
2)只能兼容到IE10
3)每访问⼀个⻚⾯都需要服务器进⾏路由匹配⽣成html ⽂件再发送响应给浏览器,消耗服务器⼤量资源
4)浏览器直接访问嵌套路由时,会报 404 问题。
5)需要在服务器配置⼀个回调路由
23、route和router的区别
route是路由信息对象,在Vue3中通过 useRoute 来获取。
1)包括了path,params,hash,query,fullPath,matched,name等路由信息参数。router是路由实例”对象,在Vue3中通过 useRouter 来获取。
1)包括了路由跳转⽅法、钩⼦函数等,⽐如:push、go、back、addRouter、beforeEnter等。
24、什么是Pinia?Pinia 和 Vuex有什么区别?
Pinia 是 Vue 的存储库,它允许您跨组件/⻚⾯共享状态。
Pinia适⽤于Vue2和Vue3,并不需要使⽤ Composition API。
Pinia的处理安装之后,它的API也同样适⽤于SSR的应⽤程序Pinia⼏个核⼼概念:
state
1)state是⼀个选项,这个选项的值需要是⼀个函数,函数返回⼀个对象,对象中存储数据
2)在组件中拿到当前的store直接使⽤即可,store.xxxgetters
1)getters也是⼀个选项,这个选项的值是⼀个对象,对象中存储着⼀个个函数,每个函数可以有⼀个参数state,通过state可以获取到当前store的state
2)除此之外每个函数还可以拿到⼀个this,这个this就是当前的整个store实例
3)通过这个this,可以想⽤谁就⽤谁
4)在组件中使⽤也是拿到store直接store.xxx即可actions
1)在actions中,主要存放⼀个个函数,每个函数最主要的⼯作发送异步请求,获取到数据后直接修改state
2)每个action函数并不像getter函数⼀样,第⼀个参数是state,它可以没有参数
3)需要通过this拿到state然后再修改state中的值
4)在组件中拿到store后直接调⽤即可,store.xxx()
5)如果你在此时传递参数,那么就可以在action中拿到参数没有模块modules的概念。
25、nextTick() 和 updated() 的区别
前者只是表示一个更新单元已完成,后者是生命周期钩子表示整个页面更新完成
26、provide/inject有什么特点?
只能自上而下、没有响应式
27、项目中你是如何封装axios的
简单的封装:
实现请求拦截:
在发送请求的时候可能需要携带一些信息在请求头上,比如 token 等
实现响应拦截
axios 请求的返回结果里面包含了很多东西,我们的业务层面通常只需要后端返回的数据即可
常见错误信息处理
在响应拦截器中封装,常见的错误信息处理
请求头设置
在请求拦截器中,设置一些请求头
api 集中式管理
28、Pinia和Vuex有什么区别?
Pinia没有Vuex中的mutations选项,因为mutations的出现解决的问题是让devtools进⾏状态追踪,但是随着技术的发展,Pinia已经解决的这个没有mutation依然可以跟踪状态的问题。
Pinia可以在任意组件中拿到store然后直接修改state中的任意值
Pinia不再需要Vuex中的modules这样的嵌套结构,取⽽代之的是可以创建⼀个个store
使⽤上的区别:
1)在Vuex中使⽤某个state时,需要$store.state.xxx。在Pinia中直接拿到store之后store.xxx即可
2)在Vuex中使⽤某个getter函数时,需要$store.getters.xxx。在Pinia中拿到store后,store.xxx即可
3)在Vuex中进⾏异步请求需要派发action函数。在Pinia中拿到store后,直接调⽤action函数即可