1、什么是MVVM?
在这里插入图片描述
(1)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。
(2)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
(3)ViewModel 层
ViewModel 通过双向数据绑定把 View 层和 Model 层连接起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
优点:
- 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
- 可重用性。可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
- 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
- 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
2、vue的生命周期?
vue的生命周期就是从创建到销毁的过程,即从创建、初始化数据、编译模板、挂载domd到渲染、更新到渲染、销毁等一系列过程。
3、vue的响应式原理/双向绑定?
当你把一个普通的 JavaScript 对象传入 Vue 实例作为data
选项,Vue 将遍历此对象所有的 property
,并使用 Object.defineProperty
把这些 property 全部转为 getter/setter
。 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property
被访问和修改时通知变更。每个组件实例都对应一个watcher
实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
检测变化的注意事项:
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
- 对于对象:对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用
Vue.set(object, propertyName, value)
- 对于数组:也可以使用
vm.$set
实例方法,或者splice
、push
、pull
方法 - 详情见官网:官网链接
4、vue异步更新队列与Vue.nextTick(callback)?
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
例如,当你设置 vm.someData = ‘new value’,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)
。这样回调函数将在 DOM 更新完成后被调用。例如:
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
详情见官网:官网链接
5、SPA单页面应用?
7、Vue 的父组件和子组件生命周期钩子函数执行顺序?
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子created -> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
8、谈谈你对 keep-alive 的了解?
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
1.一般结合路由和动态组件一起使用,用于缓存组件;
2.提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
3.对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
9、事件修饰符有哪些?
在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
:阻止事件冒泡.prevent
:拦截默认事件.capture
.self
.once
:vue3中被移除了,详细使用.passive
:不拦截默认事件
10、vue-router如何响应路由动态参数的变化?
问题:当使用路由参数时,例如从/content?id=1
到 content?id=2
,此时原来的组件实例会被复用。这也意味着组件的生命周期钩子不会再被调用,此时vue应该如何响应路由参数 的变化?
方法一:复用组件时,想对路由参数的变化作出响应的话, 可以watch (监测变化) $route
对象
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
方法二:使用 beforeRouteUpdate
守卫:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
11、vue-router有哪几种导航钩子( 导航守卫 )?
- 全局守卫:
router.beforeEach
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
- 路由独享的守卫:
beforeEnter
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
- 组件内的守卫:
beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
//不过,你可以通过传一个回调给 next来访问组件实例。
//在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
- 全局解析守卫:
router.beforeResolve
- 全局后置钩子:
router.afterEach
每个守卫方法接收三个参数:
-
to:
即将要进入的目标路由对象 -
from
: 当前导航正要离开的路由 -
next
: 一定要调用该方法来resolve这个钩子。执行效果依赖 next 方法的调用参数。
1、next():
进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed (确认的)。
2、next(false):
中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。(退出软件提示我,真要退出吗?点不确定还是本页面就可以调用next(false)
)
3、next(’/’)
或者next({ path: ‘/’ })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在router-link的 to prop或 router.push中的选项。
4、next(error):
如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
确保要调用 next方法,否则钩子就不会被 resolved
12、如何处理401页面
出现场景:
1、 未登陆用户做一些需要权限才能做的操作(例如:关注作者),代码会报出401错误。这种情况下,应该让用户回到登陆页。
2、登录用户的token过期了
当用户登陆成功之后,返回的token中有两个值,说明如下:
`
token
:
作用:在访问一些接口时,需要传入token,就是它。
有效期:2小时(安全)具体是多长,是由后端决定)。
refresh_token
:
作用: 当token的有效期过了之后,可以使用它去请求一个特殊接口(这个接口也是后端指定的,明确需要传入refresh_token),并返回一个新的token回来(有效期还是2小时),以替换过期的那个token。
有效期:14天。(最理想的情况下,一次登陆可以持续14天。)
方法一:通过axios响应拦截器来处理401问题。
参考链接
方法二:通过全局守卫处理401问题。
参考链接
13、如何处理404页面
如何触发404页面?
比如你的域名是http://localhost:8080/,当你进入一个没有声明/匹配的路由页面时就会跳到404页面,
比如访问了http://localhost:8080/无此页面,就会跳到404页面,如果没有声明一个404页面,那就会跳到一个空白页面。
解决方案:
在router.js中 路由是从上到下执行的 只需要在最后一行把path写成 * 并且指定一个404.vue页面即可
项目启动页指的是: 当你进入www.baidu.com,会自动跳转到login登录页。
// 路由懒加载
const firstPage = r => require.ensure([], () => r(require('@/components/firstPage.vue')), 'firstPage')
const login = r => require.ensure([], () => r(require('@/components/login.vue')), 'login')
const router = new Router({
mode: 'history',
routes: [
{
path: '/login',
name: '登录',
component: login
},
{
path: '/',
name: '首页',
component: firstPage
},
{
path: '*',
name: '/404',
component: resolve => require(['@/components/404.vue'], resolve),
},
]
});
14、Vue 页面权限控制的两种种方法
场景如下: 如果一个网站有不同的角色,比如 管理员 和 普通用户 ,要求不同的角色能访问的页面是不一样的。
这个时候我们就可以 把所有的页面都放在路由表里 ,只要 在访问的时候判断一下角色权限 。如果有权限就让访问,没有权限的话就拒绝访问,跳转到401页面
方法一: vue-router 在构建路由时提供了元信息 meta 配置接口
,我们可以在元信息中添加路由对应的权限,然后在路由守卫中检查相关权限,控制其路由跳转。
案例1:可以在每一个路由的 meta 属性里,将能访问该路由的角色添加到 roles 里。用户每次登陆后,将用户的角色返回。然后在访问页面时,把路由的 meta 属性和用户的角色进行对比,如果用户的角色在路由的 roles 里,那就是能访问,如果不在就拒绝访问。
路由信息:
routes: [
{
path: '/login',
name: 'login',
meta: {
roles: ['admin', 'user']
},
component: () => import('../components/Login.vue')
},
{
path: 'home',
name: 'home',
meta: {
roles: ['admin']
},
component: () => import('../views/Home.vue')
},
]
页面控制:
//假设有两种角色:admin 和 user
//从后台获取的用户角色
const role = 'user'
//当进入一个页面是会触发导航守卫 router.beforeEach 事件
router.beforeEach((to,from,next)=>{
if(to.meta.roles.includes(role)){
next() //放行
}esle{
next({path:"/404"}) //跳到404页面
}
})
案例2:可以在多个路由下面添加这个权限标识,达到控制的目的。只要一切换页面,就需要看有没有这个权限,所以可以在最大的路由下 main.js 中配置
// router.js
// 路由表元信息
[
{
path: '',
redirect: '/home'
},
{
path: '/home',
meta: {
title: 'Home',
icon: 'home'
}
},
{
path: '/userCenter',
meta: {
title: '个人中心',
requireAuth: true // 在需要登录的路由的meta中添加响应的权限标识
}
}
]
// 在守卫中访问元信息
function gaurd (to, from, next) {
// to.matched.some(record => record.meta.requireAuth)
// 可在此处
}
// router.js
// 路由表元信息
[
{
path: '',
redirect: '/home'
},
{
path: '/home',
meta: {
title: 'Home',
icon: 'home'
}
},
{
path: '/userCenter',
meta: {
title: '个人中心',
requireAuth: true // 在需要登录的路由的meta中添加响应的权限标识
}
}
]
// 在守卫中访问元信息
function gaurd (to, from, next) {
// to.matched.some(record => record.meta.requireAuth)
// 可在此处
}
方法二:将控制权限交给后端,后端根据登录人的不同,返回给前端不同的前端路由路径,并将这些路径渲染到菜单上,
14、如何根据权限进行按钮级别控制
15、Vue中v-if和v-for为何不能连用?
实质在vue2中是v-for优先执行,会创建对应的dom节点,如果v-if为false,会删除这个dom节点;这样创建后再删除,会造成页面卡顿。
16、v-model原理?
比如:v-model
在inpu
t元素上时:
第一行的代码其实只是第二行的语法糖
<input v-model="sth" />
<input v-bind:value="sth" v-on:input="sth = $event.target.value" />
详情如下:
17、slot插槽
插槽分为如下:
匿名插槽
: 不写name属性时的使用方法就叫匿名插槽,其时所谓的匿名插槽是有名字的 他的名字是default具名插槽
: 可以通过v-slot:插槽的名字 , 给指定的插槽定义内容 ,v-slot:名字可以简写为 #名字作用域插槽
: 插槽也可以通过自定义属性预备插槽需要使用的数据 而这种插槽就叫做作用域插槽,
18、computed、watch、method区别
1、三者的加载顺序不同
computed
:是在HTML DOM加载后马上执行的,如赋值;(属性将被混入到 Vue 实例)
methods
:则必须要有一定的触发条件才能执行,如点击事件
watch
:它用于观察Vue实例上的数据变动。最初绑定的时候是不会执行的,要等到监听的值第一次被改变时才执行监听计算。
默认加载的时候: 先computed再watch,不执行methods;
触发某一事件后: 先computed再methods再到watch
2、computed计算属性是基于它们的依赖进行缓存的。可以同时操作多个字段
3、watch可以实现异步请求相关的方法,只能操作一个字段
参考视频
19、watch的参数选项以及其含义
选项:deep
为了发现对象内部值的变化,可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。(监听对象必须设置deep: true)
选项:immediate
在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调,注意在带有 immediate 选项时,你不能在第一次回调时取消侦听给定的 property。
参考文档详情