44.vuex的数据丢失知道吗 怎么解决
原理:可以利用缓存,将vuex中的state,在缓存中备份一下,当状态发生改变时,同步缓存的的备份。同时当刷新时,去缓存中的备份,给state赋值
实际开发中我们一般利用vuex一个插件来实现 vuex-persistedstate
具体代码如下
安装
npm i vuex-persistedstate -S
使用
import Vuex from "vuex";
import createPersistedState from "vuex-persistedstate";
const store = new Vuex.Store({
// ...
plugins: [createPersistedState()],
});
45.vuex怎么拿数据
获取vuex state中的count数据方法有:
方法1:this.$store.state.count
直接使用;
方法2:import { mapState } from vuex
然后把...mapState('count')放入computed中
,然后直接使用count变量。
获取vuex getters中getCount数据的方法:
方法1:this.$store.getters.getCount
直接使用。
方法2:import { mapGetters } from vuex
然后把...mapGetters ('getCount')放入computed中
,然后直接使用getCount变量。
46.说说Vuex原理
vuex 是一个专门为 vue 构建的状态管理工具,主要是为了解决 多组间之间状态共享问题。强调的是集中式管理,(组件与组件之间的关系变成了组件与仓库之间的关系)
vuex 的核心包括:
- state(存放状态)、
- mutations(同步的更改状态)、
- actions(发送异步请求,拿到数据)、
- getters(根据之前的状态派发新的状态)、
- modules(模块划分)
state 发布一条新的数据,在 getters 里面根据状态派发新的状态,actions 发送异步请求获取数据,然后在 mutations 里面同步的更改数据
应用场合:购物车的数据共享、登入注册
47.vuex仓库数据很多,怎么管理
使用moduls模块划分和文件拆分来管理数据很多的问题。例如:我们可以在modules中进行模块划分,比如用户相关模块放入user中,文章信息相关模块放入article中。
代码如下:
modules:{
user:{ //跟用户相关的数据放这
state:{
},
geeters:{
},
mutations:{
},
actions:{
},
},
article:{ //跟文章相关的保存在这里
state:{
},
geeters:{
},
mutations:{
},
actions:{
},
}
}
48.vuex做数据集中管理,mutations和actions分别是做什么的,为什么不能用mutations处理异步数据
mutations和actions分别是做什么的?
mutations和action都是用来改变Vuex store的状态的;mutations提供的回调函数是同步的;而actions提供的方法是异步的,此外,actions的方法最终还是通过调用mutations的方法来实现修改vuex的状态的。
为什么不能用mutations处理异步数据?
官方文档说明:“在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你能调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念。在 Vuex 中,我们将全部的改变都用同步方式实现。我们将全部的异步操作都放在Actions中。”
actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个点子一直没时间做,那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用的异步状态变化很有帮助。
49.vuex中actions与mutations的区别
Mutation 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数;
Action Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。总体来说:actions 1、用于通过提交mutation改变数据 2、会默认将自身封装为一个Promise 3、可以包含任意的异步操作mutations 1、通过提交commit改变数据 2、只是一个单纯的函数 3、不要使用异步操作,异步操作会导致变量不能追踪
50.说说vue如何进行路由配置
一、安装
本地环境安装路由插件vue-router: cnpm install vue-router --save-dev
二、配置两种配置方法:
在main.js中 || 在src/router文件夹下的index.js中这里只说在src/router/index.js中的
1.引入 import Vue from ‘vue’ import Router from ‘vue-router’
注意这个Router是自定义的名字,这里叫这个名字后,下边都要用到的
使用/注册:Vue.use(Router)
配置配置路由:
export default new Router({
routes: [
{
path : ‘/’, //到时候地址栏会显示的路径
name : ‘Home’,
component : Home // Home是组件的名字,这个路由对应跳转到的组件。。注意component没有加“s”.
},
{
path : ‘/content’,
name : ‘Content’,
component : Content
}
],
mode: "history"
})
引入路由对应的组件地址:import Home from '@/components/Home'import Home from '@/components/Content’
在main.js中调用index.js的配置:import router from './router'
App.vue页面使用(展示)路由:<!-- 展示router -->
把这个标签放到对应位置: <router-view></router-view>
路由切换(原来的等地方):
把切换标签和链接改成:切换到Home组件切换到Content组件//这里,to里边的参数和配置时,path的路径一样即可
51.谈谈Vue路由守卫
1、路由守卫 是什么
简单来说,导航守卫就是路由跳转前、中、后过程中的一些钩子函数,这个函数能让你操作一些其他的事儿,这就是导航守卫。官方解释,vue-router提供的导航守卫主要用来通过跳转或取消的方式守卫导航。
2、路由守卫分类导航守卫分为:全局的、组件内的、单个路由独享三种
52.路由守卫中页面跳转运用了哪些钩子函数
应用场景1:可进行一些页面跳转前处理,例如判断需要登录的页面进行拦截,做登录跳转!!
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
//判断该路由是否需要登录权限
if (cookies('token')) {
//通过封装好的cookies读取token,如果存在,name接下一步如果不存在,那跳转回登录页
next()//不要在next里面加"path:/",会陷入死循环
}
else {
next({
path: '/login',
query: {redirect: to.fullPath}//将跳转的路由path作为参数,登录成功后跳转到该路由
})
}
}
else {
next()
}
})
应用场景2,进入页面登录判断、管理员权限判断、浏览器判断
//使用钩子函数对路由进行权限跳转
router.beforeEach((to, from, next) => {
const role = localStorage.getItem('ms_username');
if(!role && to.path !== '/login'){
next('/login');
}else if(to.meta.permission){
// 如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已
role === 'admin' ? next() : next('/403');
}else{
// 简单的判断IE10及以下不进入富文本编辑器,该组件不兼容
if(navigator.userAgent.indexOf('MSIE') > -1 && to.path === '/editor'){
Vue.prototype.$alert('vue-quill-editor组件不兼容IE10及以下浏览器,请使用更高版本的浏览器查看', '浏览器不兼容通知', {
confirmButtonText: '确定'
});
}else{
next();
}
}
})
应用场景3:当页面中有未关闭的窗口, 或未保存的内容时, 阻止页面跳转
beforeRouteLeave (to, from, next) {
//判断是否弹出框的状态和保存信息与否
if (this.dialogVisibility === true) {
this.dialogVisibility = false //关闭弹出框
next(false) //回到当前页面, 阻止页面跳转
}else if(this.saveMessage === false) {
alert('请保存信息后退出!') //弹出警告
next(false) //回到当前页面, 阻止页面跳转
}else {
next() //否则允许跳转
}
53.谈谈Vue路由模式,路由有哪些模式
在vue-router路由对象中,路由有两种模式:hash和history,而默认的是hash模式.前端路由目前主要有两种方法:
1、利用url的hash,就是常用的锚点(#)操作,类似页面中点击某小图标,返回页面顶部,JS通过hashChange事件来监听url的改变,IE7及以下需要轮询进行实现。一般常用框架的路由机制都是用的这种方法,例如Angualrjs自带的ngRoute和二次开发模块ui-router,react的react-route,vue-route…
2、利用HTML5的History模式,使url看起来类似普通网站,以”/”分割,没有”#”,但页面并没有跳转,不过使用这种模式需要服务器端的支持,服务器在接收到所有的请求后,都指向同一个html文件,通过historyAPI,监听popState事件,用pushState和replaceState来实现。
54.路由守卫的作用,全局和局部使用的差异是什么
全局路由守卫:就是在整个网页中,只要发生了路由的变化,都会触发。全局导航守卫主要包括两个函数,分别为:beforeEach、afterEach。局部路由守卫:(组件内的守卫)只有当前路由使用。
路由加载之前触发:beforeRouteEnter (to, from, next)
更新路由之前触发:beforeRouteUpdate (to, from, next)
离开当前路由之前触发:beforeRouteLeave (to, from, next)
55.vue路由有哪几种
this.$router.push(obj)
跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面this.$router.replace(obj)
跳转到指定url路径,但是history栈中不会有记录this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数
56.说一下vue路由跳转方式
- router-link 【实现跳转最简单的方法】<router-link to='需要跳转到的页面的路径>浏览器在解析时,将它解析成一个类似于的标签。div和css样式略
<li >
<router-link to="keyframes">点击验证动画效果 </router-link>
</li>
- this.$router.push({ path:’/user’})
- this.$router.replace{path:‘/’ }类似,不再赘述
57.vue路由的数据传参,params,query的区别。使用params什么时候会生效,什么时候不会生效,params有时会失效,为什么
query和params区别query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传,刷新页面id还在 params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失 注意:如果提供了path,params会被忽略而导致失效。 通过官网我们知道路由中的name是不能重复的,而path是可以的。所以在函数式编程中,我们可以用变量来控制路径。
58.路由守卫路由拦截如何配置
通常在项目里,我们需要用户进行登录,才能让用户查看项目。在后台管理系统中,会根据不同的用户权限展示不同的内容。在用户访问页面之前,我们通过全局前置守卫对路由进行拦截,看看你是不是可以通过。通过的标准是否登录,如果登录就通过放行,没有通过就打回。
// 不需要路由验证页面
const whiteList = ['login', 'index']
router.beforeEach((to, from, next) => {
// 确定用户是否已登录
const hasToken = false // 这里就是路由是否通过标准,一般都是通过token来验证
if (hasToken) { // 登录
if (to.path === '/login') {
// 如果已登录,请重定向到主页
next({ path: '/index' })
return
}
next()
} else {
if (whiteList.indexOf(to.name) !== -1) {
// 在免费登录白名单中,直接进入
next()
} else {
// 没有访问权限的其他页将重定向到登录页。
next(` /login`)
}
}
})
需要注意的一点是,用户没有登录,是需要跳转到登录页面,如果在白名单里面没有登录页或者没有next(),页面一直跳转直到内存溢出。
每个项目的验证是否拥有权限不一样,权限判断那一块可以根据自己的实项目需求来进行操作。
59.写公用组件的时候,怎么提高可配置性
- 带着开放封闭原则的视角思考
开放原则,是说我们需要将有可能变动的属性,通过props接口的方式暴露给组件的调用者。
封闭原则,意思是我们需要将该组件的通用能力及逻辑封装再组件内部,让调用者使用更加方便
- 组件的可配置性需要结合实际的业务需求具体分析
假设我们要封装一个Tab选项卡组件,实际功能交互可参考组件 | Element
- 组件的开放原则实践
参数 | 说明 | 类型 | 可选值 |
---|---|---|---|
value / v-model | 绑定值,选中选项卡的 name | string | |
type | 风格类型 | string | card/border-card |
closable | 标签是否可关闭 | boolean | |
addable | 标签是否可增加 | boolean | |
editable | 标签是否同时可增加和关闭 | boolean | |
tab-position | 选项卡所在位置 | string | top/right/bottom/left |
stretch | 标签的宽度是否自撑开 | boolean | |
before-leave | 切换标签之前的钩子,若返回 false 或者返回 Promise 且被 reject,则阻止切换。 | Function(activeName, oldActiveName) |
上面的表格为Tab组件提供的props配置接口,它们需要遵循如下特点,可以极大提高可配置性:
-
配置项要尽可能多的,覆盖到业务中可能出现的每一种情况。
-
保证组件在每一项配置缺省状态下都能够正常表现
-
每一项配置都应该具备合理的默认值。
组件的封闭原则实践
事件名称 | 说明 | 回调参数 |
---|---|---|
tab-click | tab 被选中时触发 | 被选中的标签 tab 实例 |
tab-remove | 点击 tab 移除按钮后触发 | 被删除的标签的 name |
tab-add | 点击 tabs 的新增按钮后触发 | |
edit | 点击 tabs 的新增按钮或 tab 被关闭后触发 | (targetName, action) |
上面的表格为Tab组件所提供的自定义事件,但是事件相关的逻辑实现,已经完全在组件内部封装好了,组件使用者,只需要按需绑定事件即可对组件的行为做出监听,这些事件也需要遵循如下特点,才能保证该组件的可配置性:
-
事件函数相关逻辑,必须在组件内部封装完善
-
自定义事件函数在触发时,必须能够返回相关的参数
例如 @tab-click 事件会给函数返回,被点击菜单的index下标等信息
- 事件函数本身也需要能够在缺省状态下,保持组件的正常运行。
60.vue-router怎么生成动态地址
动态路由配置的应用场景
一般我们在使用vue-router进行路由管理的时候,是通过如下方式配置路由与组件之间的映射关系:
// router/index.js配置文件
const router = new VueRouter({
routes:[
{
path:'/login',
component:()=>import ('../views/Login') //登录路由
},
{
path:'/reg',
component:()=>import ('../views/Reg') //注册路由
},
{
path:'/admin',
component:()=>import ('../views/Admin') //这是一个管理员才能访问的路由
},
{
path:'/vip',
component:()=>import ('../views/Vip') //假设,这是要给vip用户才能访问的路由
},
]
})
但是在后台管理平台这种类型的项目中,我们需要让拥有不同角色权限的用户,访问不同的菜单及路由,如上述代码所示,部分路由只有管理员才能访问,而另外一部分路由只能vip用户才能访问,所以需要用到vue-router提供的addRoute方法来动态管理这一部分路由配置。
2.本地只配置通用路由
我们为了实现路由的动态配置,需要将上述路由配置进行拆分,本地配置文件中,只保留通用的路由映射。
const router = new VueRouter({
routes:[
{
path:'/login',
component:()=>import ('../views/Login') //登录路由
},
{
path:'/reg',
component:()=>import ('../views/Reg') //注册路由
}
]
})
3.后端为每个用户分配一个角色,随登录接口下发给前端
app.get('/login',(req,res)=>{
//此处需要实现登录相关逻辑
res.send({
username:'张三丰',
role:'admin', //标志当前用户角色
routerList:[ //此处的路由配置,也可以通过独立接口向前端提供
{
path:'/admin',
component:()=>import ('../views/Admin') //这是一个管理员才能访问的路由
},
...此处可能会有很多其他路由,这些路由数据应该由专门的数据表来存储
]
})
})
4.前端登录并动态获取路由配置
前端登录成功后,会得到后端动态下发的,跟自己账号角色相匹配的路由数据,此时可以通过addRoute方法,将这些动态获取的路由配置数据包,设置给router对象
// views/Login.vue 登录面板
axios.get('/login',(result)=>{
let {routerList} = result
routerList.forEach((item) => {
this.$router.addRoute(item)
})
})
61.vue-router有哪些方法
router.beforeEach 路由守卫
我们可以使用这个方法,按需拦截用户访问某些敏感路由,例如:
router.beforeEach((to,from,next)=>{ //路由的全局前置守卫
if(to.path.indexOf('/account')==-1){ //判断用户访问的是不是个人中心
next() //不是个人中心,直接放行
}else{
if(store.state.my.userInfo){ //判断登录状态
next() //如果已经登录,直接放行
}else{
next('/login') //如果没有登录,则跳至登录页
}
}
})
2.router.push 编程式导航
通过编程式导航,我们可以通过事件的方式触发路由跳转
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
3.router.go、router.back、router.forward 路由的进入与返回
router.go 作用等同于window.history.go
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
4.router.addRoute 动态设置路由映射
添加一条新路由规则。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。
axios.get('/login',(result)=>{ //通过异步接口获取对应用户的特有路由配置
let {routerList} = result
routerList.forEach((item) => {
this.$router.addRoute(item) //通过addRoute方法依次将路由配置设置给router对象
})
})
62.谈谈对MVVM的理解
什么是MVVM
不管是MVC,MVP,或者MVVM,都是常见的软件架构设计模式(Architectural Pattern),它通过分离关注点来改进代码的组织方式。不同于设计模式(Design Pattern),只是为了解决一类问题而总结出的抽象方法,一种架构模式往往使用了多种设计模式。MVVM,可以拆分为Model-View-ViewModel来理解:
-
Model - 数据模型,可以对应到真实开发过程中的数据包
-
View - 视图层,布局和外观,可以对应到真实开发中的DOM结构
-
ViewModel - 扮演“View”和“Model”之间的使者,帮忙处理 View 视图层的全部业务逻辑
为什么使用MVVM框架
要回答这个问题,我们需要对比一下,在使用MVVM框架之前,我们是如何完成前端交互的。
为了修改某个视图节点中的内容信息,我们需要频繁人为操作DOM,效率低下
使用前
为了修改某个视图节点中的内容信息,我们需要频繁人为操作DOM,效率低下
var dom = document.querySelector('div');
dom.innerHTML = '张三丰';
dom.style.color = 'red';
使用后
当name数据发生变化的时候,视图区域的name自定触发更新,极大提高开发效率
<div>{{name}}</div>
data:{
name:'张三丰'
}
3.通过上述案例进一步理解MVVM
-
name数据包可以认为就是那个Model
-
div 节点可以认为就是那个View
-
而Vue提供的语法环境所支持的数据驱动能力,就可以认为是那个ViewModel
63.vue-router有什么组件
router-link 组件
<router-link>
组件支持用户在具有路由功能的应用中 (点击) 导航。通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a>
标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。<router-link>
比起写死的 <a href="...">
会好一些,理由如下:
-
无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。
-
在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面。
-
当你在 HTML5 history 模式下使用 base 选项之后,所有的 to 属性都不需要写 (基路径) 了。
router-view组件
<router-view>
组件是一个 functional 组件,渲染路径匹配到的视图组件。<router-view>
渲染的组件还可以内嵌自己的 <router-view>
,根据嵌套路径,渲染嵌套组件。
其他属性 (非 router-view 使用的属性) 都直接传给渲染的组件, 很多时候,每个路由的数据都是包含在路由参数中。
因为它也是个组件,所以可以配合 <transition>
和 <keep-alive>
使用。如果两个结合一起用,要确保在内层使用 <keep-alive>
:
<transition>
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
64.vue-cli怎么自定义组件
1.组件封装
// HelloWorld.vue组件
<template>
<div>
自定义组件
</div>
</template>
<script>
export default {
data() {
return {
key: 'value'
}
},
// 组件交互
}
</script>
<style scoped lang="less">
// 组件样式
</style>
2.局部注册调用组件
// Test.vue
<template>
<div>
<HelloWorld/>
</div>
</template>
<script>
import HelloWorld from './HelloWorld.vue'
export default {
components:{
HelloWorld
}
}
</script>
<style lang="less" scoped>
</style>
全局注册使用
先在main.js中全局注册该组件
import Vue from 'vue'
import App from './App.vue'
//全局注册
import HelloWorld from './components/HelloWorld.vue'
Vue.component('hello-world',HelloWorld)
new Vue({
render: h => h(App),
}).$mount('#app')
然后在需要使用公共组件的业务组件中,调用该组件
// Test.vue
<template>
<div>
<hello-world></hello-world>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
</style>
65.谈谈对vue-loader的理解,实现原理是什么
一. vue-loader的作用是什么
-
首先我们需要达成共识的是,目前浏览器,只能识别普通的html、css、javascript。
-
但是为了能够方便使用vue的组件化开发,需要我们将代码写在.vue单文件组件中。
-
vue文件,以及其内部的template、style、script区域代码,不能直接交给浏览器去解析,因为它解析不了。
-
所以我们需要一个vue-loader进行.vue单文件组件代码的转换,也就是
.vue方便开发 ------> vue-laoder协助翻译 -----> 浏览器才能展示
二. vue-loader 工作原理
-
vue-loader 的工作流程, 简单来说,分为以下几个步骤:
-
将一个 .vue 文件 切割成 template、script、styles 三个部分。
-
template 部分 通过 compile 生成 render、 staticRenderFns。
-
获取 script 部分 返回的配置项对象 scriptExports。
-
styles 部分,会通过 css-loader、vue-style-loader, 添加到 head 中, 或者通过 css-loader、MiniCssExtractPlugin 提取到一个 公共的css文件 中。
-
使用 vue-loader 提供的 normalizeComponent 方法, 合并 scriptExports、render、staticRenderFns, 返回 构建vue组件需要的配置项对象 - options, 即 {data, props, methods, render, staticRenderFns…}。
66.vue的路由有哪些钩子函数,可以用来做什么
一、全局守卫
顾名思义,是要定义在全局的,也就是我们 index.js 中的 router 对象。
- beforeEach
全局前置守卫,在路由跳转前触发,它在 每次导航 时都会触发。
通过 router.beforeEach
注册一个全局前置守卫。
router.beforeEach((to, from, next) => {
console.log('??~ to:', to);
console.log('??~ from:', from);
next();
})
参数
beforeEach 全局前置守卫接收三个参数
-
to: Route: 即将要进入的目标路由对象
-
from: Route: 当前导航正要离开的路由对象
-
next: Function: 一定要调用该方法不然会阻塞路由。
注意: next 参数可以不添加,但是一旦添加,则必须调用一次,否则路由跳转等会停止。
-
next()方法的几种情况
-
next(): 进行管道中的下一个钩子。
-
next(false): 中断当前的导航。回到 from 路由对应的地址。
-
next(‘/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址,可传递的参数与 router.push 中选项一致。
-
next(error): 导航终止,且该错误会被传递给 router.onError() 注册过的回调。、
- beforeResolve
全局解析守卫,在路由跳转前,所有 组件内守卫 和 异步路由组件 被解析之后触发,它同样在 每次导航 时都会触发。
通过 router.beforeResolve
注册一个全局解析守卫。
router.beforeResolve((to, from, next) => {
next();
})
回调参数,返回值和 beforeEach 一样。也可以定义多个全局解析守卫。
- afterEach
全局后置钩子,它发生在路由跳转完成后,beforeEach
和 beforeResolve
之后,beforeRouteEnter
(组件内守卫)之前。它同样在 每次导航 时都会触发。
通过 router.afterEach
注册一个全局后置钩子。
router.afterEach((to, from) => {
console.log('??~ afterEach:');
})
这个钩子的两个参数和 beforeEach 中的 to 和 from 一样。然而和其它全局钩子不同的是,这些钩子不会接受 next 函数,也不会改变导航本身。
二、路由守卫
顾名思义,就是跟路由相关的钩子,我们的路由守卫只有一个,就是 beforeEnter。
- beforeEnter
需要在路由配置上定义 beforeEnter
守卫,此守卫只在进入路由时触发,在 beforeEach
之后紧随执行,不会在 params、query
或 hash
改变时触发。
//index.js
{
path: '/a',
component: () => import('../components/A.vue'),
beforeEnter: (to, from) => {
console.log('??~ beforeEnter ');
},
},
beforeEnter
路由守卫的参数是 to、from、next
,同 beforeEach
一样。
三、组件守卫
顾名思义,是定义在路由组件内部的守卫。
- beforeRouteEnter
//A.vue
beforeRouteEnter(to, from,next) {
console.log('??~ beforeRouteEnter');
},
路由进入组件之前调用,该钩子在全局守卫 beforeEach
和路由守卫 beforeEnter
之后,全局 beforeResolve
和全局 afterEach
之前调用。
参数包括 to,from,next
。
该守卫内访问不到组件的实例,也就是 this 为 undefined,也就是他在 beforeCreate 生命周期前触发。
- beforeRouteUpdate
//A.vue
beforeRouteUpdate(to, from) {
console.log('??~ beforeRouteUpdate');
},
对于 beforeRouteUpdate
来说,this 已经可用了,所以给 next 传递回调就没有必要了。
- beforeRouteLeave
//A.vue
beforeRouteLeave(to, from) {
console.log('??~ beforeRouteLeave');
},
对于 beforeRouteLeave
来说,this 已经可用了,所以给 next 传递回调就没有必要了。
四、总结
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
67.计算属性通常用来干什么
一、理解计算属性的概念
计算属性的英文是computed,其实很多时候,一些概念从英文对照为中文后,会导致其中一些含义发生错位与丢失,我们要想更好的理解computed计算属性,可以将这个概念分为两部分来看:
计算
这里的计算,是有种宏观的概念,指的是对于数据包的一种操作,比如:筛选、过滤、新增、删除等。
说明computed本身是具有处理数据的能力。
属性
属性的意思是,一种可以读取、渲染的数据,性质跟data的作用相似
说明computed最终会给出一个数据供页面渲染使用。
由此,我们可以得出一个结论:
computed计算属性,负责将一些数据在其内部按照指定逻辑处理后,最终给出处理后的结果数据,给到组件、页面进行渲染使用,可以让开发者更加便捷的处理一些动态变化的数据需求。
二、computed计算属性的特点
-
computed内部对data做出处理,其处理结果可以像data一样,可以直接在页面中渲染。
-
computed内部逻辑会自动识别到data的变化,从而做出新的操作,得出新的数据,从而自动更新视图。
-
computed处理的数据具有缓存的能力,如果data不变,则页面中调用的都是computed第一次执行时的运算结果,提高渲染性能。
三、computed应用场景
当某个前端已经指定的data数据包,如果我们渲染前,对其有过滤、筛选等操作需求,就可以使用computed
字符串翻转的官方案例,就是将一个普通字符串翻转后再渲染到视图。
new Vue({
el:'#app', //占地盘
data:{
'msg':'Hello Vue' //自定义数据包
},
computed:{
reMsg(){ //专门用于反转msg的计算属性
return this.msg.split('').reverse().join('')
}
}
})
如果一个班级中有很多学生,每个学生通过一个对象来表达,现在我们需要根据学员成绩来进行动态切换显示:全部学员、及格学员、不及格学员,这种在本地进行筛选的需求可以快速通过computed实现,代码逻辑大致如下:
new Vue({
el:'#app', //占地盘
data:{
stu:[
{name:'张三丰',score:100},
{name:'DDK',score:50},
{name:'张翠山',score:60},
{name:'张无忌',score:90},
{name:'PDD',score:45}
],
status:0 //0全部 1及格 2不及格
},
computed:{
filterStu(){
let {stu,status} = this
// let stu = this.stu
// let status = this.status
switch (status) {
case 1: //及格
return stu.filter(item=>{
return item.score>=60
})
case 2: //不及格
let arr = stu.filter(item=>{
return item.score<60
})
return arr
default: //全部
return stu
}
}
}
})
68.computed和watch的区别
上一个问题我们已经详细探讨了computed的相关特征,在这里我们可以逐一对比一下:
两者都跟data有关,区别在于
computed 在处理完data后,会提供一个新的数据包以供使用
watch 只会监听某个指定data的变化,执行相关的逻辑,不会提供新的数据
两者的使用倾向不同
computed 内部虽然有逻辑,但是使用时更加多的关心其提供的新数据包
watch 更加关注data数据变化所引发的行为逻辑
69.状态管理器的数据走向是什么
一、什么是状态管理?
状态管理就是,把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。
二、为什么需要状态管理?
状态共享
组件之间通常会有一些共享的状态,在 Vue 或者 React 中我们一般会将这部分状态提升至公共父组件的 props 中,由父组件来统一管理共享的状态,状态的改变也是由父组件执行并向下传递。这样会导致两个问题:
需要将共享的状态提升至公共的父组件,若无公共的父组件,往往需要自行构造
状态由父组件自上而下逐层传递,若组件层级过多,数据传递会变得很冗杂
变化跟踪
在应用调试过程中,可能会有跟踪状态变化过程的需求,方便对某些应用场景的复现和回溯。这时候就需要统一对状态进行管理,并遵循特定的约定去变更状态,从而让状态的变化可预测。
三、单项数据流
因为在真实项目开发过程中,Store状态管理器中的数据会在很多组件中用到,如果不设定一个统一的规范去管理数据,最终将会导致数据混乱、使得项目变得难以维护。所以vuex状态管理器设计了如下几个核心api,与视图之间进行交互配合:
- state
vuex提供的,用以集中存储共享的数据。
- mutations
vuex提供的,专门用以触发state数据变化的方法集,并且要求mutations的方法执行结果必须时可预测的,在其内部不能出现异步请求等不可预测的逻辑。
- actions
vuex提供的,专门用于让vuex进行异步请求处理的方法集,可选择使用。
- view
视图层,整个项目组件的代称,我们在此处消费状态管理器提供的数据、方法。
数据走向必须遵循单向数据流的规范:
- 当我们初始化使用状态机数据时的流程是
store---->state----> view
- 当组件内部想要本地更新状态管理器的数据,其流程是
view触发---->mutations---->state---->store---->view更新
- 当组件内部想要在异步请求后,再更新本地状态管理器的数据,其流程是
view触发---->actions---->mutations---->state---->store---->view更新
70.vuex数据丢失怎么解决
vuex的 store 中的数据是保存在运行内存中的,当页面刷新时,页面会重新加载 vue 实例,vuex 里面的数据就会被重新赋值,这样就会出现页面刷新vuex中的数据丢失的问题。 如何解决浏览器刷新数据丢失问题呢?
方法一:手动操作本地存储
全局监听,页面刷新的时候将 store 里 state 的值存到 sessionStorage
中,然后从sessionStorage
中获取,再赋值给 store
,并移除 sessionStorage
中的数据。在 app.vue
中添加以下代码:
created() {
window.addEventListener('beforeunload',()=>{
sessionStorage.setItem('list', JSON.stringify(this.$store.state))
})
try{
sessionStorage.getItem('list') && this.$store.replaceState(Object.assign({},this.$store.state,JSON.parse(sessionStorage.getItem('list'))))
}catch(err) {
console.log(err);
}
sessionStorage.removeItem("list");
}
方法二:安装 vuex-persistedstate 插件
- npm install vuex-persistedstate -S //安装插件
- 在 store/index.js 文件中添加以下代码:
import persistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
state:{},
getters:{},
...
plugins: [persistedState()] //添加插件
})
这时候就需要使用 sessionStorage 进行存储,修改 plugins 中的代码
plugins: [
persistedState({ storage: window.sessionStorage })
]
71.怎么理解v-for的key值
key的值一般为string或则number的类型,它用于解决在进行虚拟dom更新时的更新对象快速定位,虚拟dom更新需要逐个对比虚拟dom对象绑定的数据,这个过程称为diff算法,所以key也是提升diff算法的一个标识符,因为数组可能会进行排序以及增删等动作,所以使用数组的下标来定义key是没有任何作用的
72.能不能自己实现v-model的功能
可以的,自定义一个指令,给节点绑定上添加input的功能,数据使用value绑定,在用户输入数据的时候,采用oninput事件来进行数据获取,并实时更新value数据,即可实现v-model的功能
73.Vue双向数据绑定,怎么知道数据变了
在Vue2.x中采用ES5的对象属性定义方法(Object.defineProperty)来给每一个数据添加额外属性(getter和setter属性)进行数据拦截,在vue内部实现对拦截数据的setter方法数据更新消息发布机制来获取数据更新消息,对getter方法实现消息订阅机制来获取数据更新
74.退出登录头像还在是什么原因怎么办
多方面的原因引起的:
1、头像信息是否采用了应用缓存机制,如果有需要清除H5应用缓存
2、头像缓存在webStorage中,退出没有清除,直接清除
3、缓存在移动设备的数据,也需要清除
4、缓存在vuex或redux中的数据,直接清除即可
5、浏览器缓存,清除cookie或则强制清除浏览器缓存
75.vue数据双向绑定如何实现,如果不用vue,用原生js代码怎么实现
1、获取传递进来的数据,然后对所有数据添加getter和setter方法,并在setter方法中添加消息订阅回调方法,在getter方法中实现数据更新发布回调方法
2、对作用域内的所有dom节点进行数据以来处理,根据不同指令来实现不同订阅或发布回调方法绑定:如v-model绑定的数据对象,在oninput事件中添加消息发布回调方法绑定,在v-text中添加value数据更新消息订阅回调方法绑定
当在输入框输入值的时候,出发oninput事件,出发消息发布回调方法更新数据
当数据发生变化,触发订阅方法,执行dom数据更新方法,把最新数据更新到视图上
76.vue如何实现响应式,说一下原理,它有什么缺点?
它使用ES5的Object.defineProperty
方法把所有的属性全部改为setter
和getter
属性,在每一个组件中都有一个watcher
对象,当数据被赋值或变更的时候会通知页面的render
方法对数据进行重新渲染,达到数据和视图的响应更新
因为js的固有特性,不能动态观察对象动态添加、删除属性和数组的长度添加,所以vue2.x不能够动态进行数据双向绑定,需要调用
s
e
t
、
‘
set、`
set、‘delete`这些方法来实现动态添加双向绑定属性
77.include,exclude的区别
这个是webpack中常常用于指定加载,对哪些文件进行加载的排除或包含的一个属性,include配置的文件路径中的所有文件都将采用配置的loader进行文件加载处理,exclude是配置的路径都不要进行这个加载器的处理
78.你说你使用懒加载优化页面,用的哪个版本的vue,看过源码吗, vue2.0不能实现懒加载
这个与vue没有太大关系,采用的是ES6的动态加载机制来实现页面的懒加载,主要使用的webpack语法库为:@babel/plugin-syntax-dynamic-import
,在对页面引入的时候,需要把引入方式从:import MyComponent from 'path'
修改为:const MyComponent = () => import('path')
79.运用多个组件库,提取公共代码进行压缩,发现js代码过大,怎么处理
对组件不要做全局引入,可以采用动态引入和页面局部引入机制,减少文件首次加载的文件大小和文件进行打包时把所有依赖进行一次性打包造成的文件过大问题
80.vue中爷孙通信怎么实现
可以采用:eventBus事件机制来进行数据传递;也可以采用逐层props和$emit事件传递来实现传值;vuex数据传递;使用v-model逐层数据传递等
81.vue怎么配置多个代理
在vue.config.js中使用devServer配置选项的proxy属性来进行多个代理配置
devServer: {
proxy: {
'/apis': {
target: 'http://www.baidu.com',
pathRewrite: {'/apis': ''}
},
'/info': {
target: 'http://www.sina.com',
pathRewrite: {'/info': ''}
}
}
}
82.为什么vue3.0双向数据绑定换成了proxy
因为Object.defineProperty方法的历史原因,如果要实现数据的劫持,需要遍历所有属性,如果是深层数据需要递归来进行属性绑定;而proxy是直接代理此数据对象,不需要遍历绑定属性
如果对象有数据属性添加或则属性更新,需要重新给定数据劫持方法绑定,而proxy直接代理对象,不需要对属性做另外的处理
因此可以在进行数据监听和劫持上节省很多数据遍历性能
83.你封装一个组件会有哪些考虑,用户用你的组件要怎么用?如果用户在使用你组件的同时又想自己加一些按钮进去那么此时你的组件要怎么写?
回答:
1、封装组件会有哪些考虑,用户用你的组件要怎么用?
1)、封装组件和封装函数是同样的道理,需要考虑到组件的通用性。
2)、那么,组件的属性,组件事件,组件的内容都是需要考虑到的。
组件的属性:需要考虑到属性的类型限制,属性的默认值
组件的事件:组件事件对应的函数,需要考虑到函数的参数和返回值
组件的内容:要充分考虑到组件的通用性,灵活性。组件的内容给予使用者以更大的灵活空间。
2、如果用户在使用你组件的同时又想自己加一些按钮进去那么此时你的组件要怎么写?
1)、要么使用内容的方式(vue中是插槽)
2)、要么提供一个render函数,由用户提供需要渲染的内容(如:按钮)。同时,根据自定义组件的情况,可以考虑让用户把按钮渲染在指定的区域。即:render函数里有:渲染的区域和渲染的内容。
84.vue图片懒加载:①不考虑兼容性做法(你认为最好处理最效率的)②考虑兼容性做法
回答:
1、场景:
一个网页如果包含了很多的图片,那么,服务器压力就会很大。不仅影响渲染速度还会浪费带宽。
通俗的说:你不看的图片我先不加载,也许你不看呢(哈哈),我何苦要做无效的事情呢 你想看时,我再加载(哈哈)
2、原理:
1)、先将img标签的src链接设为同一张图片(默认图片:可以是loading),把图片的实际地址赋给一个自定义属性。这时候所有的图片只发送一次请求。
2)、然后,当js监听到某张图片进入可视窗口时(说明你想看了),再将实际地址赋给src属性。src属性的值发生变化时,浏览器才会发送请求加载当前图片。如果图片没有进入可视区域,就不会加载图片(说明你还没想看呢),这样就大大的提高了效率。
3、示例代码(兼容性在代码中有注释):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
ul{
list-style: none;
}
li{
width: 100%;
height: 200px;
text-align: center;
line-height: 200px
}
img{
display: block;
height: 200px;
}
</style>
</head>
<body>
<div id="box">
</div>
</body>
</html>
<script>
let imgDoms = document.getElementsByTagName("img");
// 当前显示图片的最大下标:
let maxIndex = -1;
window.onload = function(){
// 1、从后端获取到所有的图片地址,先赋值给图片标签的自定义属性(data-src),给图片的src赋值为loading
getImgs();
// 2、加载可视区域的图片
loadingImg();
}
window.onscroll = function(){
loadingImg();
}
// 从后端获取到所有的图片地址,先赋值给图片标签的自定义属性(data-src),给图片的src赋值为loading
function getImgs(){
//这个数组中的图片,可以是从后端获取到的,也可以写死。
let imgs = ["img/1.jpg","img/2.jpg","img/3.jpg","img/4.jpg","img/5.jpg","img/6.jpg","img/7.jpg","img/8.jpg","img/9.jpg","img/10.jpg","img/11.jpg","img/12.jpg","img/13.jpg","img/14.jpg","img/15.jpg","img/16.jpg","img/17.jpg","img/18.jpg","img/19.jpg","img/20.jpg","img/21.jpg","img/22.jpg","img/23.jpg","img/24.jpg","img/25.jpg","img/26.jpg","img/27.jpg","img/28.jpg","img/29.jpg","img/30.jpg","img/31.jpg","img/32.jpg","img/33.jpg"];
let htmlStr = "";
for(let i=0;i<imgs.length;i++){
htmlStr+=`<img src="img/loading02.gif" data-src="${imgs[i]}" />`;
}
document.getElementById("box").innerHTML = htmlStr;
}
function loadingImg(){
// 1、计算当前滚动的高度+可视区域的高度(此处考虑的兼容性)
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
let clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
let height = scrollTop+clientHeight;
// 2、得到应该显示图片的序号(可视区域最下面的图片序号);
let index = Math.ceil(height/200)-1;
// 3、如果应该显示的图片的下标大于最大的下标,那就应该做图片的加载了。
if(index>maxIndex){
for(let i=maxIndex+1;i<=index;i++){
if(imgDoms[i].getAttribute("data-src")){
imgDoms[i].src = imgDoms[i].getAttribute("data-src");
imgDoms[i].removeAttribute("data-src");
}
}
}
maxIndex = index;
}
</script>
85.vue组件中的data为什么必须是函数,为什么不可以是对象,数组这些
回答:
1、如果vue组件的data是一个对象,那么在复用vue组件时,由于对象是个引用类型。那么,每个组件的data会指向同一块内存空间,组件之间的data就会互相影响。所以,组件中的data不能是对象。
2、vue框架中把data定义成函数,函数里返回真正的数据(引用类型)。每次复用组件时,vue框架都会调用该函数。当调用该函数时,函数返回新的对象(申请新的空间)。那么不同的组件的内存空间就是独立的,不会互相影响。