作者: 文章来源:
本文原地址:https://www.mimiwuqi.com/webqianduan/196306.html
目录文章目录近期整理了一下高频的前端 Vue 面试题,分享给大家一起来学习。如有问题,欢迎指正!
由于单篇文章的字数限制,所以将 Vue 篇拆分为两部分,本篇为下篇。
非懒加载:
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'
}
]
}))
Vue-Router 有两种模式:hash 模式和history 模式。默认的路由模式是 hash 模式。
简介: hash 模式是开发中默认的模式,它的 URL 带着一个#,例如:www.abc.com/#/vue,它的 hash 值就是#/vue。
特点:hash 值会出现在 URL 里面,但是不会出现在 HTTP 请求中,对后端完全没有影响。所以改变 hash 值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的 IE 浏览器也支持这种模式。hash 路由被称为是前端路由,已经成为 SPA(单页面应用)的标配。
原理: hash 模式的主要原理就是onhashchange()事件:
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
}
使用onhashchange()事件的好处就是,在页面的 hash 值发生变化时,无需向后端发起请求,window 就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash 值变化对应的 URL 都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的 hash 值和对应的 URL 关联起来了。
简介: history 模式的 URL 中没有#,它使用的是传统的路由分发模式,即用户在输入一个 URL 时,服务器会接收这个请求,并解析这个 URL,然后做出相应的逻辑处理。 特点: 当使用 history 模式时,URL 就像这样:abc.com/user/id。相比 hash 模式更加好看。但是,history 模式需要后台配置支持。如果后台没有正确配置,访问时会返回 404。 API: history api 可以分为两大部分,切换历史状态和修改历史状态:
pushState() 和 replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了 url,但浏览器不会立即向后端发送请求。如果要做到改变 url 但又不刷新页面的效果,就需要前端用上这两个 API。forward()、back()、go()三个方法,对应浏览器的前进,后退,跳转操作。虽然 history 模式丢弃了丑陋的#。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出 404 来。
如果想要切换到 history 模式,就要进行以下配置(后端也要进行配置):
const router = new VueRouter({
mode: 'history',
routes: [...]
})
调用 history.pushState() 相比于直接修改 hash,存在以下优势:
pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;pushState() 可额外设置 title 属性供后续使用。hash 模式和 history 模式都有各自的优势和缺陷,还是要根据实际情况选择性的使用。
(1)监听$route 的变化
// 监听,当路由发生变化的时候执行
watch: {
$route: {
handler: function(val, oldVal){
console.log(val);
},
// 深度观察监听
deep: true
}
},
(2)window.location.hash 读取#值
window.location.hash 的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录。
$route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数$router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。(1)param 方式
/router/:id/router/1231)路由定义
//在 APP.vue 中
<router-link :to="'/user/'+userId" replace>用户</router-link>
//在 index.js
{
path: '/user/:userid',
component: User,
},
2)路由跳转
// 方法 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)
3)参数获取 通过 $route.params.userid 获取传递的值
(2)query 方式
/router,也就是普通配置/route?id=1231)路由定义
//方式 1:直接在 router-link 标签上以对象的形式
<router-link :to="{path:'/profile',query:{name:'why',age:28,height:188}}">档案</router-link>
// 方式 2:写成按钮以点击事件形式
<button @click='profileClick'>我的</button>
profileClick(){
this.$router.push({
path: "/profile",
query: {
name: "kobi",
age: "28",
height: 198
}
});
}
2)跳转方法
// 方法 1:
<router-link :to="{ name: 'users', query: { uname: james }}">按钮</router-link>
// 方法 2:
this.$router.push({ name: 'users', query:{ uname:james }})
// 方法 3:
<router-link :to="{ path: '/user', query: { uname:james }}">按钮</router-link>
// 方法 4:
this.$router.push({ path: '/user', query:{ uname:james }})
// 方法 5:
this.$router.push('/user?uname=' + jsmes)
3)获取参数
通过 $route.query 获取传递的值
有的时候,需要通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。 为此有很多种方法可以植入路由的导航过程:全局的,单个路由独享的,或者组件级的
1. 全局路由钩子
vue-router 全局有三个路由钩子;
router.beforeEach 全局前置守卫 进入路由之前router.beforeResolve 全局解析守卫(2.5.0+)在 beforeRouteEnter 调用之后调用router.afterEach 全局后置钩子 进入路由之后具体使用∶
beforeEach(判断是否登录了,没登录就跳转到登录页):
router.beforeEach((to, from, next) => {
let ifInfo = Vue.prototype.$common.getSession('userData'); // 判断是否登录的存储信息
if (!ifInfo) {
// sessionStorage 里没有储存 user 信息
if (to.path == '/') {
//如果是登录页面路径,就直接 next()
next();
} else {
//不然就跳转到登录
Message.warning("请重新登录!");
window.location.href = Vue.prototype.$loginUrl;
}
} else {
return next();
}
})
afterEach(跳转之后滚动条回到顶部):
router.afterEach((to, from) => {
// 跳转之后滚动条回到顶部
window.scrollTo(0,0);
});
2. 单个路由独享钩子
beforeEnter 如果不想全局配置守卫的话,可以为某些路由单独配置守卫,有三个参数∶ to、from、next。
export default [
{
path: '/',
name: 'login',
component: login,
beforeEnter: (to, from, next) => {
console.log('即将进入登录页面')
next()
}
}
]
4. 组件内钩子
beforeRouteUpdate、beforeRouteEnter、beforeRouteLeave
这三个钩子都有三个参数,分别是to、from、next:
beforeRouteEnter∶ 进入组件前触发beforeRouteUpdate∶ 当前地址改变并且改组件被复用时触发,举例来说,带有动态参数的路径foo/∶id,在 /foo/1 和 /foo/2 之间跳转的时候,由于会渲染同样的 foa 组件,这个钩子在这种情况下就会被调用beforeRouteLeave∶ 离开组件被调用注意点,beforeRouteEnter组件内还访问不到this,因为该守卫执行前组件实例还没有被创建,需要传一个回调给 next 来访问,例如:
beforeRouteEnter(to, from, next) {
next(target => {
if (from.path == '/classProcess') {
target.isFromProcess = true
}
})
}
1. 完整的路由导航解析流程(不包括其他生命周期)
beforeRouteLeavebeforeEachbeforeRouteUpdatebeforeEnter。beforeRouteEnterbeforeResolveafterEach 钩子。beforeRouteEnter 守卫中传给 next 的回调函数2. 触发钩子的完整顺序
路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从 a 组件离开,第一次进入 b 组件∶
beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。beforeEach:路由全局前置守卫,可用于登录验证、全局路由 loading 等。beforeEnter:路由独享守卫beforeRouteEnter:路由组件的组件进入路由前钩子。beforeResolve:路由全局解析守卫afterEach:路由全局后置钩子beforeCreate:组件生命周期,不能访问 tAis。created:组件生命周期,可以访问 tAis,不能访问 dom。beforeMount:组件生命周期deactivated:离开缓存组件 a,或者触发 a 的beforeDestroy和destroyed组件销毁钩子。mounted:访问/操作 dom。activated:进入缓存组件,进入 a 的嵌套子组件(如果有的话)。beforeRouteEnter回调函数 next。3. 导航行为被触发到导航完成的整个过程
beforeRouteLeave。beforeEach守卫。beforeRouteUpdate 守卫(2.2+)。beforeEnteY。beforeRouteEnter。beforeResolve 守卫(2.5+),标示解析阶段完成。afterEach 钩子。beforeCreate&created、beforeMount&mountedbeforeRouteEnter守卫中传给 next 的回调函数。location.href= /url 来跳转,简单方便,但是刷新了页面;history.pushState( /url ) ,无刷新页面,静态跳转;router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为 vue-router 就是用了 history.pushState() ,尤其是在 history 模式下。用法:query 要用 path 来引入,params 要用 name 来引入,接收参数都是类似的,分别是 this.$route.query.name 和 this.$route.params.name 。
url 地址显示:query 更加类似于 ajax 中 get 传参,params 则类似于 post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示
注意:query 刷新不会丢失 query 里面的数据 params 刷新会丢失 params 里面的数据。
beforeEach、beforeResolve、afterEachbeforeEnterbeforeRouteEnter、beforeRouteUpdate、beforeRouteLeave在前端技术早期,一个 url 对应一个页面,如果要从 A 页面切换到 B 页面,那么必然伴随着页面的刷新。这个体验并不好,不过在最初也是无奈之举——用户只有在刷新页面的情况下,才可以重新去请求数据。
后来,改变发生了——Ajax 出现了,它允许人们在不刷新页面的情况下发起请求;与之共生的,还有“不刷新页面即可更新页面内容”这种需求。在这样的背景下,出现了 SPA(单页面应用)。
SPA 极大地提升了用户体验,它允许页面在不刷新的情况下更新页面内容,使内容的切换更加流畅。但是在 SPA 诞生之初,人们并没有考虑到“定位”这个问题——在内容切换前后,页面的 URL 都是一样的,这就带来了两个问题:
为了解决这个问题,前端路由出现了。
前端路由可以帮助我们在仅有一个页面的情况下,“记住”用户当前走到了哪一步——为 SPA 中的各个视图匹配一个唯一标识。这意味着用户前进、后退触发的新内容,都会映射到不同的 URL 上去。此时即便他刷新页面,因为当前的 URL 可以标识出他所处的位置,因此内容也不会丢失。
那么如何实现这个目的呢?首先要解决两个问题:
从这两个问题来看,服务端已经完全救不了这个场景了。所以要靠咱们前端自力更生,不然怎么叫“前端路由”呢?作为前端,可以提供这样的解决思路:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

Vuex 为 Vue Components 建立起了一个完整的生态圈,包括开发中的 API 调用一环。
Vue Components∶ Vue 组件。HTML 页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。dispatch∶操作行为触发方法,是唯一能执行 action 的方法。actions∶ 操作行为处理模块。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。commit∶状态改变提交操作方法。对 mutation 进行提交,是唯一能执行 mutation 的方法。mutations∶状态改变操作方法。是 Vuex 修改 state 的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。state∶ 页面状态管理容器对象。集中存储 Vuecomponents 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。getters∶ state 对象读取方法。图中没有单独列出该模块,应该被包含在了 render 中,Vue Components 通过该方法读取全局 state 对象。mutation 中的操作是一系列的同步函数,用于修改 state 中的变量的的状态。当使用 vuex 时需要通过 commit 来提交需要操作的内容。mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
state.count++ // 变更状态
}
}
})
当触发一个类型为 increment 的 mutation 时,需要调用此函数:
store.commit('increment')
而Action类似于mutation,不同点在于:
Action 可以包含任意异步操作。Action 提交的是 mutation,而不是直接变更状态。const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。 所以,两者的不同点如下:
Mutation专注于修改State,理论上是修改State的唯一途径;Action业务代码、异步请求。Mutation:必须同步执行;Action:可以异步,但不能直接操作State。actions,actions再触发mutationmutation的参数是state,它包含store中的数据;store的参数是context,它是 state 的父级,包含 state、getters(1)最重要的区别
(2)应用场景
(3)永久性
刷新页面时 vuex 存储的值会丢失,localstorage 不会。
注意: 对于不变的数据确实可以用 localstorage 可以代替 vuex,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage 无法做到,原因就是区别 1。
(1)Redux 和 Vuex 区别
Action和Reducer函数,以mutations变化函数取代 Reducer,无需switch,只需在对应的mutation函数里改变state值即可State即可store.commit提交对应的请求到 Store 中对应的mutation函数->store改变(vue 检测到数据变化自动渲染)通俗点理解就是,vuex 弱化 dispatch,通过 commit 进行 store 状态的一次更变;取消了 action 概念,不必传入特定的 action 形式进行指定变更;弱化 reducer,基于 commit 参数直接对数据进行转变,使得框架更加简易;
(2)共同思想
本质上:redux 与 vuex 都是对 mvvm 思想的服务,将数据从视图中抽离的一种方案; 形式上:vuex 借鉴了 redux,将 store 作为全局的数据中心,进行 mode 管理;
由于传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致代码无法维护。
所以需要把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,组件树构成了一个巨大的”视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为。
另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,代码将会变得更结构化且易维护。
有五种,分别是 State、Getter、Mutation 、Action、 Module。
mutation,异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。在严格模式下,无论何时发生了状态变更且不是由mutation函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
在Vuex.Store构造器选项中开启,如下:
const store = new Vuex.Store({
strict:true,
})
使用mapGetters辅助函数,利用对象展开运算符将getter混入computed对象中:
import {mapGetters} from 'vuex'
export default{
computed:{
...mapGetters(['total','discountTotal'])
}
}
使用mapMutations辅助函数,在组件中这么使用:
import { mapMutations } from 'vuex'
methods:{
...mapMutations({
setNumber:'SET_NUMBER',
})
}
然后调用this.setNumber(10)相当调用:
this.$store.commit('SET_NUMBER',10)
(1)监测机制的改变
Proxy的 observer 实现,提供全语言覆盖的反应性跟踪。Object.defineProperty 的实现所存在的很多限制:(2)只能监测属性,不能监测对象
(3)模板
(4)对象式的组件声明方式
(5)其它方面的更改
Vue 在实例初始化时遍历 data 中的所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。这样当追踪数据发生变化时,setter 会被自动调用。
Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
但是这样做有以下问题:
$set 来调用Object.defineProperty()处理。Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。相对于Object.defineProperty(),其有以下特点:
在 Vue2 中,0bject.defineProperty 会改变原始数据,而 Proxy 是创建对象的虚拟表示,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在访问或修改原始对象上的属性时进行拦截,有以下特点∶
Vue.$set 或 Vue.$delete 触发响应式。Proxy 实现的响应式原理与 Vue2 的实现原理相同,实现方式大同小异∶
在 Vue2 中,代码是 Options API 风格的,也就是通过填充 (option) data、methods、computed 等属性来完成一个 Vue 组件。这种风格使得 Vue 相对于 React 极为容易上手,同时也造成了几个问题:
this上下文,Vue 背后的一些小技巧使得 Vue 组件的开发看起来与 JavaScript 的开发原则相悖,比如在methods 中的this竟然指向组件实例来不指向methods所在的对象。这也使得 TypeScript 在 Vue2 中很不好用。于是在 Vue3 中,舍弃了 Options API,转而投向 Composition API。Composition API 本质上是将 Options API 背后的机制暴露给用户直接使用,这样用户就拥有了更多的灵活性,也使得 Vue3 更适合于 TypeScript 结合。
如下,是一个使用了 Vue Composition API 的 Vue3 组件:
<template>
<button @click="increment">
Count: {{ count }}
</button>
</template>
<script>
// Composition API 将组件属性暴露为函数,因此第一步是导入所需的函数
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
// 使用 ref 函数声明了称为 count 的响应属性,对应于 Vue2 中的 data 函数
const count = ref(0)
// Vue2 中需要在 methods option 中声明的函数,现在直接声明
function increment() {
count.value++
}
// 对应于 Vue2 中的 mounted 声明周期
onMounted(() => console.log('component mounted!'))
return {
count,
increment
}
}
}
</script>
显而易见,Vue Composition API 使得 Vue3 的开发风格更接近于原生 JavaScript,带给开发者更多地灵活性
从 React Hook 的实现角度看,React Hook 是根据 useState 调用的顺序来确定下一次重渲染时的 state 是来源于哪个 useState,所以出现了以下限制
而 Composition API 是基于 Vue 的响应式系统实现的,与 React Hook 的相比
虽然 Compositon API 看起来比 React Hook 好用,但是其设计思想也是借鉴 React Hook 的。
从本质上来说,Virtual Dom 是一个 JavaScript 对象,通过对象的方式来表示 DOM 结构。将页面的状态抽象为 JS 对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将多次 DOM 修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改 DOM 的重绘重排次数,提高渲染性能。
虚拟 DOM 是对 DOM 的抽象,这个对象是更加轻量级的对 DOM 的描述。它设计的最初目的,就是更好的跨平台,比如 Node.js 就没有 DOM,如果想实现 SSR,那么一个方式就是借助虚拟 DOM,因为虚拟 DOM 本身是 js 对象。 在代码渲染到页面之前,vue 会把代码转换成一个对象(虚拟 DOM)。以对象的形式来描述真实 DOM 结构,最终渲染到页面。在每次数据发生变化前,虚拟 DOM 都会缓存一份,变化之时,现在的虚拟 DOM 会与缓存的虚拟 DOM 进行比较。在 vue 内部封装了 diff 算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。
另外现代前端框架的一个基本要求就是无须手动操作 DOM,一方面是因为手动操作 DOM 无法保证程序性能,多人协作的项目中如果 review 不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动 DOM 操作可以大大提高开发效率。
虚拟 DOM 的解析过程:
(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能 看一下页面渲染的流程:解析 HTML -> 生成 DOM -> 生成 CSSOM -> Layout -> Paint -> Compiler 下面对比一下修改 DOM 时真实 DOM 操作和 Virtual DOM 的过程,来看一下它们重排重绘的性能消耗∶
Virtual DOM 的更新 DOM 的准备工作耗费更多的时间,也就是 JS 层面,相比于更多的 DOM 操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,依然可以给你提供过得去的性能。 (2)跨平台 Virtual DOM 本质上是 JavaScript 的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp 等。
在新老虚拟 DOM 对比时:
在 diff 中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从 O(n3)降低值 O(n),也就是说,只有当新旧 children 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。
vue 中 key 值的作用可以分为两种情况来考虑:
v-if 中使用 key。由于 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因此当使用 v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input 元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。v-for 中使用 key。用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM。key 是为 Vue 中 vnode 的唯一标记,通过这个 key,diff 操作可以更准确、更快速
a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。使用 index 作为 key 和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2…这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。
目前整理的 vue 高频面试题到这已全部更新完,后续如果有新的我们会持续更新。
更多 建站教程 请访问 https://www.mimiwuqi.com/webqianduan/