1、vue 生命周期
系统自带有8个生周期
- beforeCreate 实例创建之前,还不能访问data的属性
- created 实例创建完成,可以访问data的属性、
- beforeMount
- mouted
- beforeUpdate
- updated
- beforeDestory
- destoryed
一旦进入到页面或者组件,会执行哪些生命周期,顺序
会执行前4个
在那个阶段有data
- beforeCreate:什么也没有
- created: 有data,没有$el,
- beforeMount: 有data,没有$el
- mouted: 什么都有 data和$el
如果加入了keep-alive会多两个生命周期
- activated (组件激活时调用),
- deactivated (组件停用时调用)
如果加入keep-alive,第一次进入组件会执行哪些生命周期
- beforeCreate
- created
- beforeMount
- mouted
- activated
如果加入keep-alive,第n次进入组件会执行哪些生命周期
- activated 因为组件已经被缓存
2、谈谈你对keep-alive的了解
是什么
vue 系统自带的一个组件,功能是用来缓存组件,好处是提升性能
使用场景
用来缓存组件,提升项目性能,具体实现比如:首页进入详情页,如果用户在首页每次点击都是相同的,那么详情页就不用请求了,直接缓存起来,如果点击是不同的,那么直接请求。
3、v-if 和v-show区别
展示形式不同
- v-if 是创建一个dom节点
- v-show 是css控制显示隐藏,display:none,block
使用场景不同
- 初次加载v-if比v-show好,页面不会做加载盒子
- 频繁切换v-show比v-if好,创建和删除的开销太大,显示和隐藏的开销较小
4、v-if和v-for的优先级
- v-for的优先级比v-if的高 veu源码中体现的,function genElement
5、ref是什么
- 来获取Dom的
6、nextTick实现原理
- 获取更新后的dom内容
- 是异步的
nextTick方法主要是使用了宏任务和微任务,定义一个异步方法,多次调用nextTick会将方法存在队列中,通过这个异步方法清空当前队列。所以这个nextTick方法就是异步方法
这句话说的很乱,典型的让面试官忍不住想要深挖一探究竟的回答。(因为一听你就不是真的懂)
正确的流程应该是先去 嗅探环境,依次去检测
Promise的then -> MutationObserver的回调函数 -> setImmediate -> setTimeout 是否存在,找到存在的就使用它,以此来确定回调函数队列是以哪个 api 来异步执行。
在 nextTick 函数接受到一个 callback 函数的时候,先不去调用它,而是把它 push 到一个全局的 queue 队列中,等待下一个任务队列的时候再一次性的把这个 queue 里的函数依次执行。
这个队列可能是 microTask 队列,也可能是 macroTask 队列,前两个 api 属于微任务队列,后两个 api 属于宏任务队列。
7、scopde原理
作用
- 让样式在本组件中生效,不影响其他组件
原理
- 给节点添加自定义属性,然后css根据属性选择器添加样式
8、vue中如何做样式的穿透
scss
- 父元素 /deep/ 子元素 (class名字)
stylus
- 父元素 >>> 子元素
9、组件之间的传值通讯
父组件 传值 子组件
- props
- ref
子组件 传值 父组件
- $emit
兄弟之间传值
- eventBus
父组件 传值给 子孙组件
- provide/inject
vuex
props和children和provide/inject的主要区别:
-
props 侧重于数据的传递,并不能获取子组件里的属性和方法,适用于自定义内容的使用场景
-
$ref 侧重于获取子组件里的属性和方法,并不是太适合传递数据,并且 ref 常用于获取dom元素,起到选择器的作用
-
$children 侧重于获取所有的直接子组件,得到的是一个无序的数组,并不太适合向多个子组件传递数据
-
provide/inject 侧重于在开发高阶插件/组件库时使用,并不推荐用于普通应用程序代码中
10、computed、methods、watch有什么区别
methods
- methods中, 没有缓存,不管数据有没有变化,只要触发事件,就会调用函数
computed
- 是计算属性,有缓存,只有依赖的数据发生了变化,才会重新计算
- 不支持异步,当Computed中有异步操作时,无法监听数据的变化
watch
- watch 是监听 无缓存性,数据变化时,它就会触发相应的操作
- watch 支持异步监听
- 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
运用场景
- 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
- 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
11、props和data的优先级谁高
- props > methosd > data > computed > watch
12、vuex
状态管理仓库。
管理的是数据,方法,某一个方法,或者数据会在很多组件去使用,可以封装一个方法,放到vuex中,然后让不同的组件去用
(1)有哪些属性
- state 存放数据 (类似data)
- getter 计算属性 (类似computed)
- mutations 存放方法 (类似methods 存放方法)
- action 提交mutations
- modules 模块化,把以上四个属性在细分,让仓库更改管理
(2)action和mutation有什么区别
-
action 提交的是 mutation,而不是直接变更状态。mutation可以直接变更状态
-
action 可以包含任意异步操作。mutation只能是同步操作
-
提交方式不同,action 是用this.store.commit('SET_NUMBER',10)来提交。
(3) State
- 存数据的地方,所有数据都存在state里面
(4) getter和辅助函数mapGetters
计算属性,
示例
// store.js
import Vue from "vue"
import Vuex from "vuex"
export default new Vuex.Store({
state: {
atr: '您好',
arr: ['a','b','c']
},
getters: {
changeArr(state){
return state.arr.join('=')
}
},
mutations:{},
actions:{}
})
// vue 组件
<template>
<div>
{{str}}
{{changeArr }}
</div>
</template>
<script>
import {mapState, mapGetters} from 'vuex'
export default{
computed: {
...mapState(['str']),
...mapGetters(['changeArr'])
}
}
</script>
(5) Mutations和辅助函数mapMutations
- mutations里面存的就是一些操作数据的方法。
- 同步
// 页面使用
vue 组件需要引入import { mapMutations} from 'vuex'
在mtehods中引入
methosd: {
...mapMutations(['方法名'])
}
(6) Actions
- actions 包含一些方法,actions不能直接改变state里的数据,通过提交mutations,mutations里面包含的才是具体操作数据的方法
- 异步
使用方法同Mutations
vuex是单向数据流还是双向数据流
- vuex是单向数据流,页面不能直接修改state里面数据
vuex如何做持久化存储
vuex本身不是持久化存储,刷新页面就会初始化
- 使用localStorage自己写
- 使用vuex-persist 插件
vue设置代理
代理来解决跨域问题的
// vue.confiig.js
module.exports = {
devServer: {
proxy: '代理的url'
}
}
13、介绍一下SPA(single page web application)及SPA有什么缺点
- SPA单页面应用
缺点
- SEO优化不好,因为打包后只有一个html页面
- 性能不是特别好
14、路由的hash和history模式的区别
hash模式
-
hash模式是开发中默认的模式,它的URL带着一个#,例如:www.abc.com/#/vue,它的hash值就是
#/vue。 -
特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
history模式
- history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
- 特点: 当使用history模式时,URL就像这样:abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。 API: history api可以分为两大部分,切换历史状态和修改历史状态:
15、vue路由传值
显示传参,query传参,url可以看到参数
// query传参 http://localhost:8080/about?id=1
this.$router.push({
path: '/about',
query: {
id: 1
}
})
// 拿参数
this.$route.query.id
隐式传参, params传参,url看不到参数
// query传参 http://localhost:8080/about
this.$router.push({
name: 'about',
params: {
id: 1
}
})
// 拿参数
this.$route.params.id
16、路由导航守卫有哪些
- 全局守卫
- 组件守卫
- 独享守卫 (单个路由守卫)
全局守卫 守卫所有的页面 写在 router/index.js
1. beforeEach((to,from,next)=>{})
2. afterEach((to,from,next)=>{})
组件守卫 守卫单个组件
-
beforeRouteEnter((to,from,next)=>{}) // 路由进入之前
-
beforeRouteLeave((to,from,next)=>{}) // 路由离开之前
-
beforeRouteUpdate((to,from,next)=>{}) // 路由更新之前
总结,他们接受一个函数作为参数,函数里面有三个参数,都是to,from,next
路由独享
- beforeEnter((to,from,next)=>{})
{
path: '/mainpage',
name: 'About',
component: About, // 路由独享守卫
beforeEnter:(to,from,next) => {
if(from.name === '/mainpage/about'){
alert("这是从about来的")
}else{
alert("这不是从about来的")
} next(); // 必须调用来进行下一步操作。否则是不会跳转的
}
}
},
17、vue双向数据绑定原理
Vue 内部通过 Object.defineProperty方法属性拦截的方式,把 data 对象里每个数据的读写转化成 getter/setter,当数据变化时,通知订阅者,触发响应的回调来实现的。
具体过程包括以下几个关键步骤:
- 数据劫持:Vue遍历data中的所有属性,使用
Object.defineProperty为每个属性设置getter和setter,拦截数据访问和修改。 - 依赖收集:在getter中,Vue收集依赖该属性的Watcher实例,建立数据与视图的关联。
- 派发更新:在setter中,当数据修改时,Vue通知依赖该数据的Watcher实例进行更新。
- Watcher触发更新:Watcher收到通知后,重新执行渲染函数,生成新的虚拟DOM,通过diff算法对比新旧虚拟DOM,仅更新变化的DOM部分。
此外,Vue2的响应式系统还包括一些辅助机制:
vue2针对对象和数组怎么处理的
- 遍历对象所有可枚举属性,进行getter和setter的劫持
- 重写数组方法:push、pop、shift、unshift、splice、sort、reverse
问题补充:vue2有哪些不足:
- 对象新增属性、删除属性,需要使用Vue.set、Vue.delete
- 不能监听数组下标的变化,需要使用Vue.set
- 不能监听对象属性的删除,需要使用Vue.delete
18、什么是mvvm框架
是一个软件架构设计模式,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。
(1)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。
(2)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
(3)ViewModel 层
ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
我们以下通过一个 Vue 实例来说明 MVVM 的具体实现,有 Vue 开发经验的同学应该一目了然:
(1)View 层
<div id="app">
<p>{{message}}</p>
<button v-on:click="showMessage()">Click me</button>
</div>
(2)ViewModel 层
var app = new Vue({
el: '#app',
data: { // 用于描述视图状态
message: 'Hello Vue!',
},
methods: { // 用于描述视图行为
showMessage(){
let vm = this;
alert(vm.message);
}
},
created(){
let vm = this;
// Ajax 获取 Model 层的数据
ajax({
url: '/your/server/data/api',
success(res){
vm.message = res;
}
});
}
})
(3) Model 层
{
"url": "/your/server/data/api",
"res": {
"success": true,
"name": "IoveC",
"domain": "www.cnblogs.com"
}
}
19、vue中this.$set的使用
为什么用$set
- Vue无法检测到data中数组和对象的变化,因此也不会触发视图更新
使用方法
- Vue.set(target,key,value)
Vue不允许动态添加根级响应式属性
使用场景
- 当我们对data中的数组或对象进行修改时,有些操作方式是非响应式的,Vue检测不到数据更新,因此也不会触发视图更新。此时需要使用Vue.set()进行响应式的数据更新。
20、vue组件data为什么必须是一个函数,而vue的根实例则没有限制?
因为对象是引用类型,当组件被复用时,对象就会被共享,会造成数据混乱。
vue 组件中可能存在多个实例,使用对象形式定义data,则会导致多个实例公用一个data对象,那么状态变更将会影响有所组件实例,
-
采用函数定义,在vue源码中initdata方法中会将其作为一个工厂函数返回全新data对象,有效规避多个实例之间状态污染问题
-
而vue根实例创建过程中不存在该限制,也是因为根实例只能有一个,不需要担心这种情况
-
在创建根实例的时候,只能创建一个
// 创建根实例
const app = new Vue({
el: '#app',
data: {
counter: 1,
}
})
21、vue中key的作用和工作原理?说说你对他的理解
- key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch的过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使整个patch过程更加高效,减少DOM操作量,提高性能。
- 若不设置key,还可能在列表更新时引发一些隐藏的bug
- vue中使用相同标签元素的过渡切换时,也可以使用key属性,其目的是为了让vue可以区分他们,否则vue只会替换其内部属性而不触发过渡效果
22、DIFF算法的原理
- 首先对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点,重新创建节点进行替换
- 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点,(如果新的children没有子节点,将旧的节点移除)
- 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。
- 匹配时,找到相同的子节点,递归比较子节点
在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n3)降低值O(n),也就是说,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较
23 、Vue 如何将模板渲染成真实 DOM
24、常见的事件修饰符及其作用
.stop: 防止事件冒泡(等同于 JavaScript 中的event.stopPropagation()).prevent: 防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播)等同于 JavaScript 中的event.preventDefault().capture:与事件冒泡的方向相反,事件捕获由外到内;.self:只会触发自己范围内的事件,不包含子元素;.once:只会触发一次。
25、Vue-Router 的懒加载如何实现
非懒加载:
import List from '@/components/list.vue'
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})
(1)方案一(常用):使用箭头函数+import动态加载
const List = () => import('@/components/list.vue')
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})
(2)方案二:使用箭头函数+require动态加载
const router = new Router({
routes: [
{
path: '/list',
component: resolve => require(['@/components/list'], resolve)
}
]
})
(3)方案三:使用webpack的require.ensure技术,也可以实现按需加载。 这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
// r就是resolve
const List = r => require.ensure([], () => r(require('@/components/list')), 'list');
// 路由也是正常的写法 这种是官方推荐的写的 按模块划分懒加载
const router = new Router({
routes: [
{
path: '/list',
component: List,
name: 'list'
}
]
}))
26、 $route 和$router 的区别
- $route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数
- $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。
27、路由跳转
// 方法1:
<router-link :to="{ name: 'users', params: { uname: wade }}">按钮</router-link
// 方法2:
this.$router.push({name:'users',params:{uname:wade}})
// 方法3:
this.$router.push('/user/' + wade)
28、vue2与vue3的区别
-创建实例vue2是newVUe(),vue3是趁热阿特APP()
- 源码上vue2是js,vue3是ts
- vue2是选项是api,vue是组合式api,
- 响应式:vue2是definePropety,vue3是proxy
- 生命周期不同:
- vue2:beforeCreate、created、beforMount、munted、beforeUpdate、updated、beforeDestroy、destroyed;
- vue3:setup、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted
- vue2每个组件必须有一个根节点,vue3支持多节点
- vue2使用ts需要借助插件
- vue2支持过滤器,
$ on、$ off、$ once等方法,Vue 3:移除了过滤器,推荐使用计算属性或方法替代;$ on、$ off、$ once被移除,推荐使用mitt或EventEmitter;v-model语法有所调整,支持多个v-model绑定
29、父组件怎么监听子组件生命周期
- vue2 使用 @hook:mounted
- vue3 使用 @vue:mounted
30、v-for中key的作用
用来标识列表中每个元素的 唯一标识符,key的存在可以帮助vue更高效的更新和渲染dom
31、页面跳转时,滚动到指定模板
- 在具体页面,mounted构子中滚动到某个模块(可以通过参数判断啥的)
scrollBehavior声明路由时,配置dom、top等属性,页面存在指定元素
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: {
title: 'Home',
keepAlive: true,
scrollTo: {
el: '#about-img',
top: 0
}
}
},
{
path: '/about',
name: 'About',
component: About,
}
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior(to, from, savedPosition) {
if (to.meta.scrollTo) {
return {
el: to.meta.scrollTo.el,
top: to.meta.scrollTo.top,
behavior: 'smooth'
};
}
}
});
export default router;