当前位置:   article > 正文

【面试题】2024前端vue面试题及答案_vue前端面试题2024_2024vue前端面试题

2024vue前端面试题

Slot template


1


2


`template`不会展示到页面中,需要用先获取它的引用,然后添加到`DOM`中,



  • 1
  • 2
  • 3
  • 4
  • 5

customElements.define(‘element-details’,
class extends HTMLElement {
constructor() {
super();
const template = document
.getElementById(‘element-details-template’)
.content;
const shadowRoot = this.attachShadow({mode: ‘open’})
.appendChild(template.cloneNode(true));
}
})


在`Vue`中的概念也是如此


`Slot` 艺名插槽,花名“占坑”,我们可以理解为`solt`在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中`slot`位置),作为承载分发内容的出口


二、使用场景


通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理


如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情


通过`slot`插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用


比如布局组件、表格列、下拉选、弹框显示内容等


#### 使用vue渲染大量数据时应该怎么优化?说下你的思路!


**分析**


企业级项目中渲染大量数据的情况比较常见,因此这是一道非常好的综合实践题目。


**回答**


1. 在大型企业级项目中经常需要渲染大量数据,此时很容易出现卡顿的情况。比如大数据量的表格、树
2. 处理时要根据情况做不同处理:


* 可以采取分页的方式获取,避免渲染大量数据
* vue-virtual-scroller (opens new window)等虚拟滚动方案,只渲染视口范围内的数据
* 如果不需要更新,可以使用v-once方式只渲染一次
* 通过v-memo (opens new window)可以缓存结果,结合`v-for`使用,避免数据变化时不必要的`VNode`创建
* 可以采用懒加载方式,在用户需要的时候再加载数据,比如`tree`组件子树的懒加载


1. 还是要看具体需求,首先从设计上避免大数据获取和渲染;实在需要这样做可以采用虚表的方式优化渲染;最后优化更新,如果不需要更新可以`v-once`处理,需要更新可以`v-memo`进一步优化大数据更新性能。其他可以采用的是交互方式优化,无线滚动、懒加载等方案


#### scoped样式穿透



> 
> `scoped`虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除`scoped`属性
> 
> 
> 


1. 使用`/deep/`



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

1. 使用两个`style`标签



  • 1
  • 2
  • 3
  • 4
  • 5

#### Vue中v-html会导致哪些问题


* 可能会导致 `xss` 攻击
* `v-html` 会替换掉标签内部的子元素



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

let template = require(‘vue-template-compiler’);
let r = template.compile(<div v-html="'<span>hello</span>'"></div>)

// with(this){return _c(‘div’,{domProps: {“innerHTML”:_s(‘hello’)}})}
console.log(r.render);

// _c 定义在core/instance/render.js
// _s 定义在core/instance/render-helpers/index,js
if (key === ‘textContent’ || key === ‘innerHTML’) {
if (vnode.children) vnode.children.length = 0
if (cur === oldProps[key]) continue // #6601 work around Chrome version <= 55 bug where single textNode // replaced by innerHTML/textContent retains its parentNode property
if (elm.childNodes.length === 1) {
elm.removeChild(elm.childNodes[0])
}
}


#### 如果让你从零开始写一个vuex,说说你的思路


**思路分析**


这个题目很有难度,首先思考`vuex`解决的问题:存储用户全局状态并提供管理状态API。


* `vuex`需求分析
* 如何实现这些需求


**回答范例**


1. 官方说`vuex`是一个状态管理模式和库,并确保这些状态以可预期的方式变更。可见要实现一个`vuex`


* 要实现一个`Store`存储全局状态
* 要提供修改状态所需API:`commit(type, payload), dispatch(type, payload)`


1. 实现`Store`时,可以定义`Store`类,构造函数接收选项`options`,设置属性`state`对外暴露状态,提供`commit`和`dispatch`修改属性`state`。这里需要设置`state`为响应式对象,同时将`Store`定义为一个`Vue`插件
2. `commit(type, payload)`方法中可以获取用户传入`mutations`并执行它,这样可以按用户提供的方法修改状态。 `dispatch(type, payload)`类似,但需要注意它可能是异步的,需要返回一个`Promise`给用户以处理异步结果


**实践**


`Store`的实现:



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

class Store {
constructor(options) {
this.state = reactive(options.state)
this.options = options
}
commit(type, payload) {
this.options.mutations[type].call(this, this.state, payload)
}
}


**vuex简易版**



  • 1
  • 2
  • 3
  • 4
  • 5

/**

  • 1 实现插件,挂载$store
  • 2 实现store
    */

let Vue;

class Store {
constructor(options) {
// state响应式处理
// 外部访问: this.$store.state.***
// 第一种写法
// this.state = new Vue({
// data: options.state
// })

// 第二种写法:防止外界直接接触内部vue实例,防止外部强行变更
this._vm = new Vue({
  data: {
    $$state: options.state
  }
})

this._mutations = options.mutations
this._actions = options.actions
this.getters = {}
options.getters && this.handleGetters(options.getters)

this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

}

get state () {
return this._vm._data.$$state
}

set state (val) {
return new Error(‘Please use replaceState to reset state’)
}

handleGetters (getters) {
Object.keys(getters).map(key => {
Object.defineProperty(this.getters, key, {
get: () => getterskey
})
})
}

commit (type, payload) {
let entry = this._mutations[type]
if (!entry) {
return new Error(${type} is not defined)
}

entry(this.state, payload)
  • 1

}

dispatch (type, payload) {
let entry = this._actions[type]
if (!entry) {
return new Error(${type} is not defined)
}

entry(this, payload)
  • 1

}
}

const install = (_Vue) => {
Vue = _Vue

Vue.mixin({
beforeCreate () {
if (this.KaTeX parse error: Expected '}', got 'EOF' at end of input: … Vue.prototype.store = this.$options.store
}
},
})
}

export default { Store, install }


验证方式



  • 1
  • 2
  • 3
  • 4
  • 5

import Vue from ‘vue’
import Vuex from ‘./vuex’
// this.$store
Vue.use(Vuex)

export default new Vuex.Store({
state: {
counter: 0
},
mutations: {
// state从哪里来的
add (state) {
state.counter++
}
},
getters: {
doubleCounter (state) {
return state.counter * 2
}
},
actions: {
add ({ commit }) {
setTimeout(() => {
commit(‘add’)
}, 1000)
}
},
modules: {
}
})


参考 [前端进阶面试题详细解答]( )


#### Vue与Angular以及React的区别?


Vue与AngularJS的区别


* `Angular`采用`TypeScript`开发, 而`Vue`可以使用`javascript`也可以使用`TypeScript`
* `AngularJS`依赖对数据做脏检查,所以`Watcher`越多越慢;`Vue.js`使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
* `AngularJS`社区完善, `Vue`的学习成本较小


Vue与React的区别


**相同点:**


1. `Virtual DOM`。其中最大的一个相似之处就是都使用了`Virtual DOM`。(当然`Vue`是在`Vue2.x`才引用的)也就是能让我们通过操作数据的方式来改变真实的`DOM`状态。因为其实`Virtual DOM`的本质就是一个`JS`对象,它保存了对真实`DOM`的所有描述,是真实`DOM`的一个映射,所以当我们在进行频繁更新元素的时候,改变这个`JS`对象的开销远比直接改变真实`DOM`要小得多。
2. 组件化的开发思想。第二点来说就是它们都提倡这种组件化的开发思想,也就是建议将应用分拆成一个个功能明确的模块,再将这些模块整合在一起以满足我们的业务需求。
3. `Props`。`Vue`和`React`中都有`props`的概念,允许父组件向子组件传递数据。
4. 构建工具、Chrome插件、配套框架。还有就是它们的构建工具以及Chrome插件、配套框架都很完善。比如构建工具,`React`中可以使用`CRA`,`Vue`中可以使用对应的脚手架`vue-cli`。对于配套框架`Vue`中有`vuex、vue-router`,`React`中有`react-router、redux`。


**不同点**


1. 模版的编写。最大的不同就是模版的编写,`Vue`鼓励你去写近似常规`HTML`的模板,`React`推荐你使用`JSX`去书写。
2. 状态管理与对象属性。在`React`中,应用的状态是比较关键的概念,也就是`state`对象,它允许你使用`setState`去更新状态。但是在`Vue`中,`state`对象并不是必须的,数据是由`data`属性在`Vue`对象中进行管理。
3. 虚拟`DOM`的处理方式不同。`Vue`中的虚拟`DOM`控制了颗粒度,组件层面走`watcher`通知,而组件内部走`vdom`做`diff`,这样,既不会有太多`watcher`,也不会让`vdom`的规模过大。而`React`走了类似于`CPU`调度的逻辑,把`vdom`这棵树,微观上变成了链表,然后利用浏览器的空闲时间来做`diff`


#### Vue项目中你是如何解决跨域的呢


一、跨域是什么


跨域本质是浏览器基于**同源策略**的一种安全手段


同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能


所谓同源(即指在同一个域)具有以下三个相同点


* 协议相同(protocol)
* 主机相同(host)
* 端口相同(port)


反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域



> 
> 一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。
> 
> 
> 


#### Class 与 Style 如何动态绑定


`Class` 可以通过对象语法和数组语法进行动态绑定


对象语法:



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

data: {
isActive: true,
hasError: false
}


数组语法:



  • 1
  • 2
  • 3
  • 4
  • 5

data: {
activeClass: ‘active’,
errorClass: ‘text-danger’
}


`Style` 也可以通过对象语法和数组语法进行动态绑定


对象语法:



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

data: {
activeColor: ‘red’,
fontSize: 30
}


数组语法:



  • 1
  • 2
  • 3
  • 4
  • 5

data: {
styleColor: {
color: ‘red’
},
styleSize:{
fontSize:‘23px’
}
}


#### 了解history有哪些方法吗?说下它们的区别



> 
> `history` 这个对象在`html5`的时候新加入两个`api` `history.pushState()` 和 `history.repalceState()` 这两个`API`可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。
> 
> 
> 


从参数上来说:



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

window.history.pushState(state,title,url)
//state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取
//title:标题,基本没用,一般传null
//url:设定新的历史纪录的url。新的url与当前url的origin必须是一样的,否则会抛出错误。url可以时绝对路径,也可以是相对路径。
//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, ‘./qq/’),则变成 https://www.baidu.com/a/qq/,
//执行history.pushState(null, null, ‘/qq/’),则变成 https://www.baidu.com/qq/

window.history.replaceState(state,title,url)
//与pushState 基本相同,但她是修改当前历史纪录,而 pushState 是创建新的历史纪录


另外还有:


* `window.history.back()` 后退
* `window.history.forward()`前进
* `window.history.go(1)` 前进或者后退几步


从触发事件的监听上来说:


* `pushState()`和`replaceState()`不能被`popstate`事件所监听
* 而后面三者可以,且用户点击浏览器前进后退键时也可以


#### 在Vue中使用插件的步骤


* 采用`ES6`的`import ... from ...`语法或`CommonJS`的`require()`方法引入插件
* 使用全局方法`Vue.use( plugin )`使用插件,可以传入一个选项对象`Vue.use(MyPlugin, { someOption: true })`


#### `$route`和`$router`的区别


* `$route`是“路由信息对象”,包括`path`,`params`,`hash`,`query`,`fullPath`,`matched`,`name`等路由信息参数。
* 而`$router`是“路由实例”对象包括了路由的跳转方法,钩子函数等


#### 为什么要使用异步组件


1. 节省打包出的结果,异步组件分开打包,采用`jsonp`的方式进行加载,有效解决文件过大的问题。
2. 核心就是包组件定义变成一个函数,依赖`import()` 语法,可以实现文件的分割加载。



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

components:{
AddCustomerSchedule:(resolve)=>import(“…/components/AddCustomer”) // require([])
}


原理



  • 1
  • 2
  • 3
  • 4
  • 5

export function ( Ctor: Class | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array, tag?: string ): VNode | Array | void {
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend
// 第二次渲染时Ctor不为undefined
if (Ctor === undefined) {
return createAsyncPlaceholder( // 渲染占位符 空虚拟节点
asyncFactory,
data,
context,
children,
tag
)
}
}
}
function resolveAsyncComponent ( factory: Function, baseCtor: Class ): Class | void {
if (isDef(factory.resolved)) {
// 3.在次渲染时可以拿到获取的最新组件
return factory.resolved
}
const resolve = once((res: Object | Class) => {
factory.resolved = ensureCtor(res, baseCtor)
if (!sync) {
forceRender(true) //2. 强制更新视图重新渲染
} else {
owners.length = 0
}
})
const reject = once(reason => {
if (isDef(factory.errorComp)) {
factory.error = true forceRender(true)
}
})
const res = factory(resolve, reject)// 1.将resolve方法和reject方法传入,用户调用 resolve方法后
sync = false
return factory.resolved
}


#### 函数式组件优势和原理


**函数组件的特点**


1. 函数式组件需要在声明组件是指定 `functional:true`
2. 不需要实例化,所以没有`this`,`this`通过`render`函数的第二个参数`context`来代替
3. 没有生命周期钩子函数,不能使用计算属性,`watch`
4. 不能通过`$emit` 对外暴露事件,调用事件只能通过`context.listeners.click`的方式调用外部传入的事件
5. 因为函数式组件是没有实例化的,所以在外部通过`ref`去引用组件时,实际引用的是`HTMLElement`
6. 函数式组件的`props`可以不用显示声明,所以没有在`props`里面声明的属性都会被自动隐式解析为`prop`,而普通组件所有未声明的属性都解析到`$attrs`里面,并自动挂载到组件根元素上面(可以通过`inheritAttrs`属性禁止)


**优点**


1. 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件
2. 函数式组件结构比较简单,代码结构更清晰


**使用场景:**


* 一个简单的展示组件,作为容器组件使用 比如 `router-view` 就是一个函数式组件
* “高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件


例子



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

Vue.component(‘functional’,{ // 构造函数产生虚拟节点的
functional:true, // 函数式组件 // data={attrs:{}}
render(h){
return h(‘div’,‘test’)
}
})
const vm = new Vue({
el: ‘#app’
})


源码相关



  • 1
  • 2
  • 3
  • 4
  • 5

// functional component
if (isTrue(Ctor.options.functional)) { // 带有functional的属性的就是函数式组件
return createFunctionalComponent(Ctor, propsData, data, context, children)
}

// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on // 处理事件
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn // 处理原生事件

// install component management hooks onto the placeholder node
installComponentHooks(data) // 安装组件相关钩子 (函数式组件没有调用此方法,从而性能高于普通组件)


### Vue.set的实现原理


* 给对应和数组本身都增加了`dep`属性
* 当给对象新增不存在的属性则触发对象依赖的`watcher`去更新
* 当修改数组索引时,我们调用数组本身的`splice`去更新数组(数组的响应式原理就是重新了`splice`等方法,调用`splice`就会触发视图更新)


**基本使用**



> 
> 以下方法调用会改变原始数组:`push()`, `pop()`, `shift()`, `unshift()`, `splice()`, `sort()`, `reverse()`,`Vue.set( target, key, value )`
> 
> 
> 


* 调用方法:`Vue.set(target, key, value )`
	+ `target`:要更改的数据源(可以是对象或者数组)
	+ `key`:要更改的具体数据
	+ `value` :重新赋的值



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
{{user.name}} {{user.age}}

**相关源码**



  • 1
  • 2
  • 3
  • 4
  • 5

// src/core/observer/index.js 44
export class Observer { // new Observer(value)
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data

constructor (value: any) {
this.value = value
this.dep = new Dep() // 给所有对象类型增加dep属性
}
}



  • 1
  • 2

// src/core/observer/index.js 201
export function set (target: Array | Object, key: any, val: any): any {
// 1.是开发环境 target 没定义或者是基础类型则报错
if (process.env.NODE_ENV !== ‘production’ &&
(isUndef(target) || isPrimitive(target))
) {
warn(Cannot set reactive property on undefined, null, or primitive value: ${(target: any)})
}
// 2.如果是数组 Vue.set(array,1,100); 调用我们重写的splice方法 (这样可以更新视图)
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
// 利用数组的splice变异方法触发响应式
target.splice(key, 1, val)
return val
}
// 3.如果是对象本身的属性,则直接添加即可
if (key in target && !(key in Object.prototype)) {
target[key] = val // 直接修改属性值
return val
}
// 4.如果是Vue实例 或 根数据data时 报错,(更新_data 无意义)
const ob = (target: any).ob
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== ‘production’ && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ’ +
‘at runtime - declare it upfront in the data option.’
)
return val
}
// 5.如果不是响应式的也不需要将其定义成响应式属性
if (!ob) {
target[key] = val
return val
}
// 6.将属性定义成响应式的
defineReactive(ob.value, key, val)
// 通知视图更新
ob.dep.notify()
return val
}


**我们阅读以上源码可知,vm.$set 的实现原理是:**


* **如果目标是数组** ,直接使用数组的 `splice` 方法触发相应式;
* **如果目标是对象** ,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 `defineReactive` 方法进行响应式处理( `defineReactive` 方法就是 `Vue` 在初始化对象时,给对象属性采用 `Object.defineProperty` 动态添加 `getter` 和 `setter` 的功能所调用的方法)


#### Vue为什么没有类似于React中shouldComponentUpdate的生命周期


* 考点: `Vue`的变化侦测原理
* 前置知识: 依赖收集、虚拟`DOM`、响应式系统



> 
> 根本原因是`Vue`与`React`的变化侦测方式有所不同
> 
> 
> 


* 当React知道发生变化后,会使用`Virtual Dom Diff`进行差异检测,但是很多组件实际上是肯定不会发生变化的,这个时候需要 `shouldComponentUpdate` 进行手动操作来减少`diff`,从而提高程序整体的性能
* `Vue`在一开始就知道那个组件发生了变化,不需要手动控制`diff`,而组件内部采用的`diff`方式实际上是可以引入类似于`shouldComponentUpdate`相关生命周期的,但是通常合理大小的组件不会有过量的diff,手动优化的价值有限,因此目前`Vue`并没有考虑引入`shouldComponentUpdate`这种手动优化的生命周期


#### vue-router中如何保护路由


**分析**


路由保护在应用开发过程中非常重要,几乎每个应用都要做各种路由权限管理,因此相当考察使用者基本功。


**体验**


全局守卫:



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

const router = createRouter({ … })

router.beforeEach((to, from) => {
// …
// 返回 false 以取消导航
return false
})


路由独享守卫:



  • 1
  • 2
  • 3
  • 4
  • 5

const routes = [
{
path: ‘/users/:id’,
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]


组件内的守卫:



  • 1
  • 2
  • 3
  • 4
  • 5

const UserDetails = {
template: ...,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
},
}


**回答**


* `vue-router`中保护路由的方法叫做路由守卫,主要用来通过跳转或取消的方式守卫导航。
* 路由守卫有三个级别:`全局`、`路由独享`、`组件级`。影响范围由大到小,例如全局的`router.beforeEach()`,可以注册一个全局前置守卫,每次路由导航都会经过这个守卫,因此在其内部可以加入控制逻辑决定用户是否可以导航到目标路由;在路由注册的时候可以加入单路由独享的守卫,例如`beforeEnter`,守卫只在进入路由时触发,因此只会影响这个路由,控制更精确;我们还可以为路由组件添加守卫配置,例如`beforeRouteEnter`,会在渲染该组件的对应路由被验证前调用,控制的范围更精确了。
* 用户的任何导航行为都会走`navigate`方法,内部有个`guards`队列按顺序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会取消原有的导航。


**原理**


`runGuardQueue(guards)`链式的执行用户在各级别注册的守卫钩子函数,通过则继续下一个级别的守卫,不通过进入`catch`流程取消原本导航



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

// 源码
runGuardQueue(guards)
.then(() => {
// check global guards beforeEach
guards = []
for (const guard of beforeGuards.list()) {
guards.push(guardToPromiseFn(guard, to, from))
}
guards.push(canceledNavigationCheck)

return runGuardQueue(guards)
  • 1

})
.then(() => {
// check in components beforeRouteUpdate
guards = extractComponentsGuards(
updatingRecords,
‘beforeRouteUpdate’,
to,
from
)

for (const record of updatingRecords) {
  record.updateGuards.forEach(guard => {
    guards.push(guardToPromiseFn(guard, to, from))
  })
}
guards.push(canceledNavigationCheck)

// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

})
.then(() => {
// check the route beforeEnter
guards = []
for (const record of to.matched) {
// do not trigger beforeEnter on reused views
if (record.beforeEnter && !from.matched.includes(record)) {
if (isArray(record.beforeEnter)) {
for (const beforeEnter of record.beforeEnter)
guards.push(guardToPromiseFn(beforeEnter, to, from))
} else {
guards.push(guardToPromiseFn(record.beforeEnter, to, from))
}
}
}
guards.push(canceledNavigationCheck)

// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
  • 1
  • 2

})
.then(() => {
// NOTE: at this point to.matched is normalized and does not contain any () => Promise

// clear existing enterCallbacks, these are added by extractComponentsGuards
to.matched.forEach(record => (record.enterCallbacks = {}))

// check in-component beforeRouteEnter
guards = extractComponentsGuards(
  enteringRecords,
  'beforeRouteEnter',
  to,
  from
)
guards.push(canceledNavigationCheck)

// run the queue of per route beforeEnter guards
return runGuardQueue(guards)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

})
.then(() => {
// check global guards beforeResolve
guards = []
for (const guard of beforeResolveGuards.list()) {
guards.push(guardToPromiseFn(guard, to, from))
}
guards.push(canceledNavigationCheck)

return runGuardQueue(guards)
  • 1

})
// catch any navigation canceled
.catch(err =>
isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)
? err
: Promise.reject(err)
)


[源码位置(opens new window)]( )")


#### Vue-router 路由钩子在生命周期的体现


一、Vue-Router导航守卫


有的时候,需要通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。 为此有很多种方法可以植入路由的导航过程:全局的,单个路由独享的,或者组件级的


1. 全局路由钩子


vue-router全局有三个路由钩子;


* router.beforeEach 全局前置守卫 进入路由之前
* router.beforeResolve 全局解析守卫(2.5.0+)在 beforeRouteEnter 调用之后调用
* router.afterEach 全局后置钩子 进入路由之后


具体使用∶


* beforeEach(判断是否登录了,没登录就跳转到登录页)



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

router.beforeEach((to, from, next) => {
let ifInfo = Vue.prototype.KaTeX parse error: Expected '}', got 'EOF' at end of input: … Vue.prototype.loginUrl;
}
} else {
return next();
}
})


* afterEach (跳转之后滚动条回到顶部)



  • 1
  • 2
  • 3
  • 4
  • 5

router.afterEach((to, from) => {
// 跳转之后滚动条回到顶部
window.scrollTo(0,0);
});


1. 单个路由独享钩子


**beforeEnter** 如果不想全局配置守卫的话,可以为某些路由单独配置守卫,有三个参数∶ to、from、next



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

export default [
{
path: ‘/’,
name: ‘login’,
component: login,
beforeEnter: (to, from, next) => {
console.log(‘即将进入登录页面’)
next()
}
}
]


1. 组件内钩子


beforeRouteUpdate、beforeRouteEnter、beforeRouteLeave


这三个钩子都有三个参数∶to、from、next


* beforeRouteEnter∶ 进入组件前触发
* beforeRouteUpdate∶ 当前地址改变并且改组件被复用时触发,举例来说,带有动态参数的路径foo/∶id,在 /foo/1 和 /foo/2 之间跳转的时候,由于会渲染同样的foa组件,这个钩子在这种情况下就会被调用
* beforeRouteLeave∶ 离开组件被调用


注意点,beforeRouteEnter组件内还访问不到this,因为该守卫执行前组件实例还没有被创建,需要传一个回调给 next来访问,例如:



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

beforeRouteEnter(to, from, next) {
next(target => {
if (from.path == ‘/classProcess’) {
target.isFromProcess = true
}
})
}


二、Vue路由钩子在生命周期函数的体现


1. 完整的路由导航解析流程(不包括其他生命周期)


* 触发进入其他路由。
* 调用要离开路由的组件守卫beforeRouteLeave
* 调用局前置守卫∶ beforeEach
* 在重用的组件里调用 beforeRouteUpdate
* 调用路由独享守卫 beforeEnter。
* 解析异步路由组件。
* 在将要进入的路由组件中调用 beforeRouteEnter
* 调用全局解析守卫 beforeResolve
* 导航被确认。
* 调用全局后置钩子的 afterEach 钩子。
* 触发DOM更新(mounted)。
* 执行beforeRouteEnter 守卫中传给 next 的回调函数


1. 触发钩子的完整顺序


路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件∶


* beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
* beforeEach:路由全局前置守卫,可用于登录验证、全局路由loading等。
* beforeEnter:路由独享守卫
* beforeRouteEnter:路由组件的组件进入路由前钩子。
* beforeResolve:路由全局解析守卫
* afterEach:路由全局后置钩子


### 分享



![](https://img-blog.csdnimg.cn/img_convert/173f06f98b5862f7043f8d2121d6c595.png)  

![](https://img-blog.csdnimg.cn/img_convert/1e75212cfef88fb7de5e60b4a621fe4c.png)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/930850
推荐阅读
相关标签
  

闽ICP备14008679号