21 vue中的路由有哪些模式?有什么区别?
hash 模 式 ( 默 认 ) :路 由 的 改 变 依 托 于 # 锚 点 , 锚 点 后 边 的 值 可 以 通 过 修 改window.location.hash 的值来修改,每一次 hash 值的变化都会导致触发 hashchange 这个事件,从而实现路由切换,但 url 改变时,页面不会重新加载。
history 模式:由于 H5 标准的发布,pushState()和 replace()这两个 API 的提供可以让我们改变 url 的地址并且不会发送请求,不仅如此我们还可以读取浏览器的历史栈,并对历史记录栈进行修改,以及 popState(),当浏览器跳转到新的状态时,将触发 popState 事件。
两者的区别:
(1)hash 模式只能改变锚点后面的 url 片段,但是 pushState 设置的 url 可以是与当前url 同源的任意 url;
(2)history 模式会将 url 修改的和正常请求后端的 url 一样,会导致浏览器向后台发送请求,而 hash 模式就不会导致浏览器向后台发送请求;
(3)hash 模式下,浏览器会记录下所有发生变化的 url,浏览器的前进和后退就都可以使用。history 模式下,前进和后退并不会使得页面状态退回或者前进到上一个或者下一个状态。
注意:在使用 hash 模式时,页面刷新可能会出现 404 的情况,所以必须使服务端对每一个可能的 path 都有相应的映射。
22 vue中的style标签中的scoped属性有什么作用?
scoped 属性会自动添加一个唯一的 attribute 标识 (比如 data-v-d47fa855) 为组件内 CSS 指定作用域,编译的时候 .container:hover 会被编译成类似 .container[data-v-d47fa855]:hover,例如:
图片
另:Vue 的单文件组件里的样式设置是非常灵活的。通过 vue-loader,你可以使用任意预处理器、后处理器,甚至深度集成 CSS Modules——全部都在
23 vue中路由跳转方式有哪些?
(1)router-link 跳转
此种跳转方式是在 template 模板中进行跳转,可传参可不传参,如下:
<!-- 1.不带参数 -->
<!-- name和path都行,建议用name -->
<!-- 注意:router-link中的连接如果是'/'开始就是从根路由开始,
反之,不带'/',则从当前路由开始 -->
<router-link :to="{ name: 'home' }"></router-link>
<router-link :path="{ path: '/home' }"></router-link>
<!-- 2.带参数 -->
<router-link :to="{ name: 'home', params: { id: 1 } }"></router-link>
<!-- params传参(类似post),
需要在路由文件中配置path: "/home/:id",或者path: "/home:id",
不配置path,第一次可请求,刷新页面id会消失,
配置path,刷新页面id会保留,
html获取参数$route.params.id,
script获取参数this.$route.params.id
-->
<router-link :to="{ name: 'home', query: { id: 1 } }"></router-link>
<!-- query传参(类似get,url后面会显示参数),
在路由文件中不可配置
html获取参数$route.query.id,
script获取参数this.$route.query.id
-->
(2)this.$router.push()跳转
此种跳转方式需要在函数中调用,可传参可不传参,如下:
// 1.不带参数
this.$router.push('/home');
this.$router.push({ name: 'home' });
this.$router.push({ path: '/home' });
// 2.query传参
this.$router.push({ name: 'home', query: { id: '1' }});
this.$router.push({ path: '/home', query: { id: '1' }});
// html获取参数$route.query.id
// script获取参数this.$route.query.id
// 3.params传参
this.$router.push({ name: 'home', params: { id: '1' }});
// 路由文件中配置path: "/home/:id",或者path: "/home:id",
// 不配置path,第一次可请求,刷新页面id会消失,
// 配置path,刷新页面id会保留,
// html获取参数$route.params.id,
// script获取参数this.$route.params.id
注意:query 和 params 的区别
query 类似 get, 跳转之后页面 url 后面会拼接参数,类似?id=1, 非重要性的可以这样传,密码之类还是用 params 刷新页面 id 还在params 类似 post, 跳转之后页面 url 后面不会拼接参数 , 但是刷新页面 id 会消失。
(3)this.$router.replace()跳转
此种方式的使用跟 this.$router.push()是一样的,但是两者有区别!
(4)this.$router.go(n)跳转
此种方式是向前或者向后跳转 n 个页面,n 可为正整数或负整数
push()、replace()和go()的区别:
this.$router.push():跳转到指定url路径,并向history栈中添加一个记录,点击后退会返回上一个页面。
this.$router.replace():跳转到指定url路径,但是history栈中不会有记录,点击后退会跳转到上上一个页面,就是直接替换了当前页面。
this.$router.go(n):向前或者向后跳转 n 个页面,n 可为正整数或负整数。
24 vue-router跳转和location.href有什么区别
(1)vue-router 使用 pushState 进行路由更新,静态跳转,页面不会重新加载;location.href 会触发浏览器,页面重新加载一次;
(2)vue-router 使用 diff 算法,实现按需加载,减少 dom 操作;
(3)vue-router 是路由跳转或同一个页面跳转;location.href 是不同页面间跳转;
(4)vue-router 是异步加载 this.$nextTick(()=>{获取 url));location.href是同步加载。
25 如何动态设置class/style
26 对象新增/删除属性无法更新视图是为什么
原因:Object.defineProperty 没有对对象的新属性进行属性劫持。
对象新属性无法更新视图:使用 Vue. s e t ( o b j , k e y , v a l u e ) ,组件中 t h i s . set(obj, key, value),组件中this. set(obj,key,value),组件中this.set(obj,key, value)。
删除属性无法更新视图:使用 Vue. d e l e t e ( o b j , k e y ) ,组件中 t h i s . delete(obj, key),组件中 this. delete(obj,key),组件中this.delete(obj,key)。
27 使用vue-router切换页面时怎么设置过渡动画
(1)单个路由设置过渡,在各路由组件内使用 并设置不同的 name
(2)基于路由设置动态过渡效果首先在路由文件中,每个路径的路由元信息 meta 中设置个 depth 属性值,用来表示其路由的深度(层级);给进行 name 的属性绑定;然后通过 watch 监听 $route,根据 to、from 元信息中的 depth 值的大小,设置不同的跳转动画。
watch: {
‘$route’ (to, from) {
const toDepth = to.path.split(‘/’).length
const fromDepth = from.path.split(‘/’).length
this.transitionName = toDepth < fromDepth ? ‘slide-right’ : ‘slide-left’
}
}
28 vue如何解决数据层级过深的问题
数据层级过深:在 vue 数据层级过深时,会出现无法实时进行数据渲染的问题,底层的数据已经变了,但是渲染出来的数据没有变化。
解决方案:
(1)层级过深的数据,如果数据经常性的变化,尽量不要放在计算属性(computed)中,请使用侦听器(watch)来监管数据,watch 中设置 deep: true;
(2)进行数据切割。以层级为标准进行切割,分成多份数据;
(3)使用 Vue.set()手动更新更新数据,配合使用 n e x t T i c k 回调函数,将赋值的操作放在 nextTick 回调函数,将赋值的操作放在 nextTick回调函数,将赋值的操作放在nextTick 回调中进行。
29 vue项目中遇到哪些难题
(1)使用 keep-alive 包裹的组件/路由,打开一次后 created 只会执行一次,有两种情况,如果要重新渲染部分数据,可以在 activated 中做处理;路由/组件重新重新created,可以使用官方推荐的:key=“key” ,然后去改变 key 的值,组件就会重新挂载了。
(2)beforeRouteEnter 中的 next 函数的执行时间是在组件 mounted 之后,因此需要在此处处理的数据要注意了。
(3)网页刷新时 vuex 数据会丢失,需配合 localStorage 或 sessionStorage 使用,把必须数据先存后取。
(4)对于权限及不确定路由,可以使用 addRoutes(),可以避免抖动。
(5)熟练使用 es6 的数组 map、find、filter 等方法,对解构赋值、class 继承、promise,及 es7 中的 async 和 await。
(6)使用 computed 替代 watch,computed 依赖于 data 属性的更改,是有缓存的。
(7)通过 props 传递的值,不要在子组件去更改。开发中,如果直接更改 props,基本类型的值会报错;引用类型的值不会报错,但是不好去追溯数据的更改,很多人不太注意引用类型,可通过 computed 或 watch 去更改。
(8)在 data 里调用 methods 的方法,可以在 data 里定义 let self = this,然后在使用 self.xx()进行调用。
30 页面第一次加载会依次触发哪些钩子函数
页面第一次加载会依次触发 beforeCreate、created、beforeMount、mounted 钩子。
31 r o u t e r 和 router和 router和route的区别
this.$route:当前激活的路由的信息对象。每个对象都是局部的,可以获取当前路由的 path, name, params, query 等属性。
this.$router:全局的 router 实例。通过 vue 根实例中注入 router 实例,然后再注入到每个子组件,从而让整个应用都有路由功能。其中包含了很多属性和对象(比如history 对象),任何页面也都可以调用其 push()、replace()、go() 等方法。
32 vue的内置指令有哪些
v-pre:跳过这个元素和它的子元素的编辑过程。可以用来显示原始Mustache标签,跳过大量没有指令的节点会加快编译。
v-slot:缩写为#,提供具名插槽或需要接收prop的插槽。
v-model:在表单控件或者组件上创建双向绑定。
v-bind:简写为:,用来进行属性绑定。
v-on:绑定事件时用,简写为@。
v-for:列表循环遍历数组、对象、字符串、数字等。
v-text:更新元素的textContent,如果需要更新部分的textContent,需要时用{{ Mustache }}插值。
v-html:更新元素的innerHTML,注意:内部按普通HTML插入,不会做为Vue模板进行编译,如果视图时用v-html组合模板,可以重新考虑是否通过使用组件来代替。
v-show:根据表达式之真假值,切换元素的display值;当条件变化时该指令触发过渡效果。
v-if:根据表达式之真假值,来有条件地渲染元素,在切换时元素及它的数据绑定/组件被销毁并重建,如果元素是,将提出它的内容做为条件。
33 vue中nextTick()的作用、原理和应用场景
1、首先要了解下 js 的运行机制(Event Loop):
JS 执行是单线程的,它是基于事件循环的。
(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,会存在一个任务队列,只要异步任务有了结果,就在任务队列中放置一个事件。
(3)当执行栈中的所有同步任务执行完后,就会读取任务队列。那些对应的异步任务,会结束等待状态,进入执行栈。
主线程不断重复前 3 步。
这里主线程的执行过程就是一个 tick,而所有的异步结果都是通过任务队列来调度。Event Loop 分为宏任务和微任务,无论是执行宏任务还是微任务,完成后都会进入到一下tick,并在两个 tick 之间进行 UI 渲染。
由于 Vue DOM 更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的 DOM,所以设置了 Vue.nextTick()方法
2、什么是 nextTick()?
官方解释:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,例如:当你想设置 vm.someData = 'new value’时,该组件不会立刻重新渲染,这会影响我们在 DOM 更新后的操作。所以,为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback),也就是为了可以获取更新后的 DOM。
3、什么时候使用?
Vue 生命周期的 created()钩子函数进行的 DOM 操作一定要放在 Vue.nextTick()的回调函数中在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的 DOM 结构的时候,这个操作应该放在 Vue.nextTick()的回调函数中。
34 vue3有哪些新的特性
(1)专门为组件提供了新的属性:setup();
注:在 Vue3 中,定义 methods、watch、computed、data 数据都放在了 setup()函数中;setup()函数会在 created()生命周期之前执行。
(2)接收 props 数据
props 是 setup()函数的一个形参,组件接收的 props 数据可以在 setup()函数内访问到,例如:
setup(props) {
console.log(props.p1)
}
(3)context 上下文对象
context 是 setup() 的第二个形参,它是一个上下文对象,可以通过 context 来访问Vue 的实例 this。
注意:在 setup() 函数中访问不到 Vue 的 this 实例。
(4)reactive()
reactive() 函数接收一个普通的对象,返回出一个响应式对象。在 Vue2.x 的版本中,我们只需要在 data() 中定义一个数据就能将它变为响应式数据,在 Vue3.0 中,需要用 reactive 函数或者 ref 来创建响应式数据,例如:
// 在组件库中引入 reactive
import { reactive } from ‘vue’
setup() {
// 创建响应式对象
const state = reactive({count:0});
// 将响应式对象 return 出去,暴露给模板使用
return state;
}
(5)ref()
ref() 函数可以根据给定的值来创建一个响应式的数据对象,返回值是一个对象,且只包含一个 .value 属性。
ref 的注意事项:
在 setup() 函数内,由 ref() 创建的响应式数据返回的是对象,所以需要用 .value来访问;而在 setup() 函数外部则不需要 .value ,直接访问即可。可以在 reactive 对象中访问 ref() 函数创建的响应式数据。新的 ref() 会覆盖旧的 ref()。
(6)此外,还有 computed()、readonly()、watchEffect()、watch()等更新。
(7)此外,还有组合式 API 的依赖方法集:isRef()、toRefs()等的使用。
35 怎么解决spa(单页面)首屏加载比较慢的问题
(1)使用路由懒加载,将路由资源进行划分,减少第一次加载的时长;
(2)使用 cdn,减少代码打包体积,提升请求速度,还可以避免服务器带宽问题;
(3)极限打包压缩静态文件,首屏图片等可以使用 lazy-load;
(4)线上生产环境,将打包后的.map 辅助测试文件删除(体积很大);
(5)借助打包优化分析工具,查看体积较大的文件,再相应的使用 gzip 等进行压缩或者cdn 的引入。
36 vue3.0为什么用Proxy API替代defineProperty API
响应式优化。
(1)defineProperty API 的局限性最大原因是它只能针对单例属性做监听。
Vue2.x 中的响应式实现正是基于 defineProperty 中的 descriptor,对 data 中的属性做了遍历 + 递归,为每个属性设置了 getter、setter。
这也就是为什么 Vue 只能对 data 中预定义过的属性做出响应的原因,在 Vue 中使用下标的方式直接修改属性的值或者添加一个预先不存在的对象属性是无法做到 setter 监听的,这是 defineProperty 的局限性。
(2)Proxy API 的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性,将会带来很大的性能提升和更优的代码。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
(3)响应式是惰性的
在 Vue.js 2.x 中,对于一个深层属性嵌套的对象,要劫持它内部深层次的变化,就需要递归遍历这个对象,执行 Object.defineProperty 把每一层对象数据都变成响应式的,这无疑会有很大的性能消耗。
在 Vue.js 3.0 中,使用 Proxy API 并不能监听到对象内部深层次的属性变化,因此它的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部属性才会变成响应式,简单的可以说是按需实现响应式,减少性能消耗。
37 vue3.0编译做了哪些优化?
(1)生成 Block tree
Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,单个组件内部 需要遍历该组件的整个 vnode 树。在 2.0 里,渲染效率的快慢与组件大小成正相关:组件越大,渲染效率越慢。并且,对于一些静态节点,又无数据更新,这些遍历都是性能浪费。
Vue.js 3.0 做到了通过编译阶段对静态模板的分析,编译生成了 Block tree。Block tree是一个将模版基于动态节点指令切割的嵌套区块,每个 区块内部的节点结构是固定的,
每个区块只需要追踪自身包含的动态节点。所以,在 3.0 里,渲染效率不再与模板大小成正相关,而是与模板中动态节点的数量成正相关。
(2)slot 编译优化
Vue.js 2.x 中,如果有一个组件传入了 slot,那么每次父组件更新的时候,会强制使子组件 update,造成性能的浪费。
Vue.js 3.0 优化了 slot 的生成,使得非动态 slot 中属性的更新只会触发子组件的更新。动态 slot 指的是在 slot 上面使用 v-if,v-for,动态 slot 名字等会导致 slot 产生运行时动态变化但是又无法被子组件 track 的操作。
(3)diff 算法优化
Vue2.x 中的虚拟 dom 是进行全量的对比。Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容化。
38 vue3.0是如何变得更快的?
(1)diff 方法优化
Vue2.x 中的虚拟 dom 是进行全量的对比。
Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容化。
(2)hoistStatic 静态提升
Vue2.x : 无论元素是否参与更新,每次都会重新创建。
Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。
(3)cacheHandlers 事件侦听器缓存
默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
39 对vuex的认识
1、vuex 可以理解为一种开发模式或框架。比如 PHP 有 thinkphp,java 有 spring 等。
2、通过状态(数据源)集中管理驱动组件的变化(好比 spring 的 IOC 容器对 bean 进行集中管理)。
3、应用级的状态集中放在 store 中;改变状态的方式是提交 mutations,这是个同步的事物;异步逻辑应该封装在 action 中。
40 vuex有哪些属性,运用场景
(1)vuex 可以理解为数据的集中管理。当 vue 组件读取 vuex 中数据时,若 vuex中的数据发生变化,那么相应的组件也会得到更新。
(2)属性:有五种,分别是 State、 Getter、Mutations 、Action、 Module。
State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
总的来说,我们可以在组件中触发 Action,Action 则会提交 Mutations,Mutaions 会对 State 进行修改,组件再根据 State 、Getter 渲染页面。
(3)应用场景:vuex 主要用于跨组件的数据通信,可以做登录及未登录状态的检测、购物车、公共组件封装等。