Vue3.0 为什么要用 proxy?
在 Vue2 中, 0bject.defineProperty 会扭转原始数据,而 Proxy 是创建对象的虚构示意,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在拜访或批改原始对象上的属性时进行拦挡,有以下特点∶
- 不需用应用
Vue.$set
或Vue.$delete
触发响应式。 - 全方位的数组变化检测,打消了Vue2 有效的边界状况。
- 反对 Map,Set,WeakMap 和 WeakSet。
Proxy 实现的响应式原理与 Vue2的实现原理雷同,实现形式大同小异∶
- get 收集依赖
- Set、delete 等触发依赖
- 对于汇合类型,就是对汇合对象的办法做一层包装:原办法执行后执行依赖相干的收集或触发逻辑。
说说你对slot的了解?slot应用场景有哪些
一、slot是什么
在HTML中 slot
元素 ,作为 Web Components
技术套件的一部分,是Web组件内的一个占位符
该占位符能够在前期应用本人的标记语言填充
举个栗子
<template id="element-details-template"> <slot name="element-name">Slot template</slot></template><element-details> <span slot="element-name">1</span></element-details><element-details> <span slot="element-name">2</span></element-details>
template
不会展现到页面中,须要用先获取它的援用,而后增加到DOM
中,
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渲染大量数据时应该怎么优化?说下你的思路!
剖析
企业级我的项目中渲染大量数据的状况比拟常见,因而这是一道十分好的综合实际题目。
答复
- 在大型企业级我的项目中常常须要渲染大量数据,此时很容易呈现卡顿的状况。比方大数据量的表格、树
- 解决时要依据状况做不同解决:
- 能够采取分页的形式获取,防止渲染大量数据
- vue-virtual-scroller (opens new window)等虚构滚动计划,只渲染视口范畴内的数据
- 如果不须要更新,能够应用v-once形式只渲染一次
- 通过v-memo (opens new window)能够缓存后果,联合
v-for
应用,防止数据变动时不必要的VNode
创立 - 能够采纳懒加载形式,在用户须要的时候再加载数据,比方
tree
组件子树的懒加载 - 还是要看具体需要,首先从设计上防止大数据获取和渲染;切实须要这样做能够采纳虚表的形式优化渲染;最初优化更新,如果不须要更新能够
v-once
解决,须要更新能够v-memo
进一步优化大数据更新性能。其余能够采纳的是交互方式优化,无线滚动、懒加载等计划
scoped款式穿透
scoped
尽管防止了组件间款式净化,然而很多时候咱们须要批改组件中的某个款式,然而又不想去除scoped
属性
- 应用
/deep/
<!-- Parent --><template><div class="wrap"> <Child /></div></template><style lang="scss" scoped>.wrap /deep/ .box{ background: red;}</style><!-- Child --><template> <div class="box"></div></template>
- 应用两个
style
标签
<!-- Parent --><template><div class="wrap"> <Child /></div></template><style lang="scss" scoped>/* 其余款式 */</style><style lang="scss">.wrap .box{ background: red;}</style><!-- Child --><template> <div class="box"></div></template>
Vue中v-html会导致哪些问题
- 可能会导致
xss
攻打 v-html
会替换掉标签外部的子元素
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('<span>hello</span>')}})} console.log(r.render);// _c 定义在core/instance/render.js // _s 定义在core/instance/render-helpers/index,jsif (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
需要剖析- 如何实现这些需要
答复范例
- 官网说
vuex
是一个状态管理模式和库,并确保这些状态以可预期的形式变更。可见要实现一个vuex
- 要实现一个
Store
存储全局状态 - 要提供批改状态所需API:
commit(type, payload), dispatch(type, payload)
- 实现
Store
时,能够定义Store
类,构造函数接管选项options
,设置属性state
对外裸露状态,提供commit
和dispatch
批改属性state
。这里须要设置state
为响应式对象,同时将Store
定义为一个Vue
插件 commit(type, payload)
办法中能够获取用户传入mutations
并执行它,这样能够按用户提供的办法批改状态。dispatch(type, payload)
相似,但须要留神它可能是异步的,须要返回一个Promise
给用户以解决异步后果
实际
Store
的实现:
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 实现插件,挂载$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) } 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: () => getters[key](this.state) }) }) } commit (type, payload) { let entry = this._mutations[type] if (!entry) { return new Error(`${type} is not defined`) } entry(this.state, payload) } dispatch (type, payload) { let entry = this._actions[type] if (!entry) { return new Error(`${type} is not defined`) } entry(this, payload) }}const install = (_Vue) => { Vue = _Vue Vue.mixin({ beforeCreate () { if (this.$options.store) { Vue.prototype.$store = this.$options.store } }, })}export default { Store, install }
验证形式
import Vue from 'vue'import Vuex from './vuex'// this.$storeVue.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的区别
相同点:
Virtual DOM
。其中最大的一个相似之处就是都应用了Virtual DOM
。(当然Vue
是在Vue2.x
才援用的)也就是能让咱们通过操作数据的形式来扭转实在的DOM
状态。因为其实Virtual DOM
的实质就是一个JS
对象,它保留了对实在DOM
的所有形容,是实在DOM
的一个映射,所以当咱们在进行频繁更新元素的时候,扭转这个JS
对象的开销远比间接扭转实在DOM
要小得多。- 组件化的开发思维。第二点来说就是它们都提倡这种组件化的开发思维,也就是倡议将利用分拆成一个个性能明确的模块,再将这些模块整合在一起以满足咱们的业务需要。
Props
。Vue
和React
中都有props
的概念,容许父组件向子组件传递数据。- 构建工具、Chrome插件、配套框架。还有就是它们的构建工具以及Chrome插件、配套框架都很欠缺。比方构建工具,
React
中能够应用CRA
,Vue
中能够应用对应的脚手架vue-cli
。对于配套框架Vue
中有vuex、vue-router
,React
中有react-router、redux
。
不同点
- 模版的编写。最大的不同就是模版的编写,
Vue
激励你去写近似惯例HTML
的模板,React
举荐你应用JSX
去书写。 - 状态治理与对象属性。在
React
中,利用的状态是比拟要害的概念,也就是state
对象,它容许你应用setState
去更新状态。然而在Vue
中,state
对象并不是必须的,数据是由data
属性在Vue
对象中进行治理。 - 虚构
DOM
的解决形式不同。Vue
中的虚构DOM
管制了颗粒度,组件层面走watcher
告诉,而组件外部走vdom
做diff
,这样,既不会有太多watcher
,也不会让vdom
的规模过大。而React
走了相似于CPU
调度的逻辑,把vdom
这棵树,宏观上变成了链表,而后利用浏览器的闲暇工夫来做diff
Vue我的项目中你是如何解决跨域的呢
一、跨域是什么
跨域实质是浏览器基于同源策略的一种平安伎俩
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最外围也最根本的平安性能
所谓同源(即指在同一个域)具备以下三个相同点
- 协定雷同(protocol)
- 主机雷同(host)
- 端口雷同(port)
反之非同源申请,也就是协定、端口、主机其中一项不雷同的时候,这时候就会产生跨域
肯定要留神跨域是浏览器的限度,你用抓包工具抓取接口数据,是能够看到接口曾经把数据返回回来了,只是浏览器的限度,你获取不到数据。用postman申请接口可能申请到数据。这些再次印证了跨域是浏览器的限度。
Class 与 Style 如何动静绑定
Class
能够通过对象语法和数组语法进行动静绑定
对象语法:
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>data: { isActive: true, hasError: false}
数组语法:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>data: { activeClass: 'active', errorClass: 'text-danger'}
Style
也能够通过对象语法和数组语法进行动静绑定
对象语法:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>data: { activeColor: 'red', fontSize: 30}
数组语法:
<div v-bind:style="[styleColor, styleSize]"></div>data: { styleColor: { color: 'red' }, styleSize:{ fontSize:'23px' }}
理解history有哪些办法吗?说下它们的区别
history
这个对象在html5
的时候新退出两个api
history.pushState()
和history.repalceState()
这两个API
能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录。
从参数上来说:
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
是“路由实例”对象包含了路由的跳转办法,钩子函数等
为什么要应用异步组件
- 节俭打包出的后果,异步组件离开打包,采纳
jsonp
的形式进行加载,无效解决文件过大的问题。 - 外围就是包组件定义变成一个函数,依赖
import()
语法,能够实现文件的宰割加载。
components:{ AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([]) }
原理
export function ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | 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<Component> ): Class<Component> | void { if (isDef(factory.resolved)) { // 3.在次渲染时能够拿到获取的最新组件 return factory.resolved } const resolve = once((res: Object | Class<Component>) => { 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 }
函数式组件劣势和原理
函数组件的特点
- 函数式组件须要在申明组件是指定
functional:true
- 不须要实例化,所以没有
this
,this
通过render
函数的第二个参数context
来代替 - 没有生命周期钩子函数,不能应用计算属性,
watch
- 不能通过
$emit
对外裸露事件,调用事件只能通过context.listeners.click
的形式调用内部传入的事件 - 因为函数式组件是没有实例化的,所以在内部通过
ref
去援用组件时,理论援用的是HTMLElement
- 函数式组件的
props
能够不必显示申明,所以没有在props
外面申明的属性都会被主动隐式解析为prop
,而一般组件所有未声明的属性都解析到$attrs
外面,并主动挂载到组件根元素下面(能够通过inheritAttrs
属性禁止)
长处
- 因为函数式组件不须要实例化,无状态,没有生命周期,所以渲染性能要好于一般组件
- 函数式组件构造比较简单,代码构造更清晰
应用场景:
- 一个简略的展现组件,作为容器组件应用 比方
router-view
就是一个函数式组件 - “高阶组件”——用于接管一个组件作为参数,返回一个被包装过的组件
例子
Vue.component('functional',{ // 构造函数产生虚构节点的 functional:true, // 函数式组件 // data={attrs:{}} render(h){ return h('div','test') }})const vm = new Vue({ el: '#app'})
源码相干
// functional componentif (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 listenersconst 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 nodeinstallComponentHooks(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
:从新赋的值
<div id="app">{{user.name}} {{user.age}}</div><div id="app"></div><script> // 1. 依赖收集的特点:给每个属性都减少一个dep属性,dep属性会进行收集,收集的是watcher // 2. vue会给每个对象也减少一个dep属性 const vm = new Vue({ el: '#app', data: { // vm._data user: {name:'poetry'} } }); // 对象的话:调用defineReactive在user对象上定义一个age属性,减少到响应式数据中,触发对象自身的watcher,ob.dep.notify()更新 // 如果是数组 通过调用 splice办法,触发视图更新 vm.$set(vm.user, 'age', 20); // 不能给根属性增加,因为给根增加属性 性能耗费太大,须要做很多解决 // 批改必定是同步的 -> 更新都是一步的 queuewatcher</script>
相干源码
// src/core/observer/index.js 44export 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属性 }}
// src/core/observer/index.js 201export function set (target: Array<any> | 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中如何爱护路由
剖析
路由爱护在利用开发过程中十分重要,简直每个利用都要做各种路由权限治理,因而相当考查使用者基本功。
体验
全局守卫:
const router = createRouter({ ... })router.beforeEach((to, from) => { // ... // 返回 false 以勾销导航 return false})
路由独享守卫:
const routes = [ { path: '/users/:id', component: UserDetails, beforeEnter: (to, from) => { // reject the navigation return false }, },]
组件内的守卫:
const UserDetails = { template: `...`, beforeRouteEnter(to, from) { // 在渲染该组件的对应路由被验证前调用 }, beforeRouteUpdate(to, from) { // 在以后路由扭转,然而该组件被复用时调用 }, beforeRouteLeave(to, from) { // 在导航来到渲染该组件的对应路由时调用 },}
答复
vue-router
中爱护路由的办法叫做路由守卫,次要用来通过跳转或勾销的形式守卫导航。- 路由守卫有三个级别:
全局
、路由独享
、组件级
。影响范畴由大到小,例如全局的router.beforeEach()
,能够注册一个全局前置守卫,每次路由导航都会通过这个守卫,因而在其外部能够退出管制逻辑决定用户是否能够导航到指标路由;在路由注册的时候能够退出单路由独享的守卫,例如beforeEnter
,守卫只在进入路由时触发,因而只会影响这个路由,管制更准确;咱们还能够为路由组件增加守卫配置,例如beforeRouteEnter
,会在渲染该组件的对应路由被验证前调用,管制的范畴更准确了。 - 用户的任何导航行为都会走
navigate
办法,外部有个guards
队列按程序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会勾销原有的导航。
原理
runGuardQueue(guards)
链式的执行用户在各级别注册的守卫钩子函数,通过则持续下一个级别的守卫,不通过进入catch
流程勾销本来导航
// 源码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) }) .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) }) .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) }) .then(() => { // NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component> // 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) }) .then(() => { // check global guards beforeResolve guards = [] for (const guard of beforeResolveGuards.list()) { guards.push(guardToPromiseFn(guard, to, from)) } guards.push(canceledNavigationCheck) return runGuardQueue(guards) }) // catch any navigation canceled .catch(err => isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED) ? err : Promise.reject(err) )
源码地位(opens new window)
Vue-router 路由钩子在生命周期的体现
一、Vue-Router导航守卫
有的时候,须要通过路由来进行一些操作,比方最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就勾销跳转,并跳到登录页面让其登录。
为此有很多种办法能够植入路由的导航过程:全局的,单个路由独享的,或者组件级的
- 全局路由钩子
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);});
- 单个路由独享钩子
beforeEnter 如果不想全局配置守卫的话,能够为某些路由独自配置守卫,有三个参数∶ to、from、next
export default [ { path: '/', name: 'login', component: login, beforeEnter: (to, from, next) => { console.log('行将进入登录页面') next() } }]
- 组件内钩子
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 } }) }
二、Vue路由钩子在生命周期函数的体现
- 残缺的路由导航解析流程(不包含其余生命周期)
- 触发进入其余路由。
- 调用要来到路由的组件守卫beforeRouteLeave
- 调用局前置守卫∶ beforeEach
- 在重用的组件里调用 beforeRouteUpdate
- 调用路由独享守卫 beforeEnter。
- 解析异步路由组件。
- 在将要进入的路由组件中调用 beforeRouteEnter
- 调用全局解析守卫 beforeResolve
- 导航被确认。
- 调用全局后置钩子的 afterEach 钩子。
- 触发DOM更新(mounted)。
- 执行beforeRouteEnter 守卫中传给 next 的回调函数
- 触发钩子的残缺程序
路由导航、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。
- 导航行为被触发到导航实现的整个过程
- 导航行为被触发,此时导航未被确认。
- 在失活的组件里调用来到守卫 beforeRouteLeave。
- 调用全局的 beforeEach守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnteY。
- 解析异步路由组件(如果有)。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+),标示解析阶段实现。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 非重用组件,开始组件实例的生命周期:beforeCreate&created、beforeMount&mounted
- 触发 DOM 更新。
- 用创立好的实例调用 beforeRouteEnter守卫中传给 next 的回调函数。
- 导航实现
Vue-router 导航守卫有哪些
- 全局前置/钩子:beforeEach、beforeResolve、afterEach
- 路由独享的守卫:beforeEnter
- 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
Vue的diff算法详细分析
1. 是什么
diff
算法是一种通过同层的树节点进行比拟的高效算法
其有两个特点:
- 比拟只会在同层级进行, 不会跨层级比拟
- 在diff比拟的过程中,循环从两边向两头比拟
diff
算法在很多场景下都有利用,在 vue
中,作用于虚构 dom
渲染成实在 dom
的新旧 VNode
节点比拟
2. 比拟形式
diff
整体策略为:深度优先,同层比拟
- 比拟只会在同层级进行, 不会跨层级比拟
- 比拟的过程中,循环从两边向两头收拢
上面举个vue
通过diff
算法更新的例子:
新旧VNode
节点如下图所示:
第一次循环后,发现旧节点D与新节点D雷同,间接复用旧节点D作为diff
后的第一个实在节点,同时旧节点endIndex
挪动到C,新节点的 startIndex
挪动到了 C
第二次循环后,同样是旧节点的开端和新节点的结尾(都是 C)雷同,同理,diff
后创立了 C 的实在节点插入到第一次创立的 D 节点前面。同时旧节点的 endIndex
挪动到了 B,新节点的 startIndex
挪动到了 E
第三次循环中,发现E没有找到,这时候只能间接创立新的实在节点 E,插入到第二次创立的 C 节点之后。同时新节点的 startIndex
挪动到了 A。旧节点的 startIndex
和 endIndex
都放弃不动
第四次循环中,发现了新旧节点的结尾(都是 A)雷同,于是 diff
后创立了 A 的实在节点,插入到前一次创立的 E 节点前面。同时旧节点的 startIndex
挪动到了 B,新节点的startIndex
挪动到了 B
第五次循环中,情景同第四次循环一样,因而 diff
后创立了 B 实在节点 插入到前一次创立的 A 节点前面。同时旧节点的 startIndex
挪动到了 C,新节点的 startIndex 挪动到了 F
新节点的 startIndex
曾经大于 endIndex
了,须要创立 newStartIdx
和 newEndIdx
之间的所有节点,也就是节点F,间接创立 F 节点对应的实在节点放到 B 节点前面
3. 原理剖析
当数据产生扭转时,set
办法会调用Dep.notify
告诉所有订阅者Watcher
,订阅者就会调用patch
给实在的DOM
打补丁,更新相应的视图
源码地位:src/core/vdom/patch.js
function patch(oldVnode, vnode, hydrating, removeOnly) { if (isUndef(vnode)) { // 没有新节点,间接执行destory钩子函数 if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] if (isUndef(oldVnode)) { isInitialPatch = true createElm(vnode, insertedVnodeQueue) // 没有旧节点,间接用新节点生成dom元素 } else { const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { // 判断旧节点和新节点本身一样,统一执行patchVnode patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) } else { // 否则间接销毁及旧节点,依据新节点生成dom元素 if (isRealElement) { if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true) return oldVnode } } oldVnode = emptyNodeAt(oldVnode) } return vnode.elm } }}
patch
函数前两个参数位为oldVnode
和 Vnode
,别离代表新的节点和之前的旧节点,次要做了四个判断:
- 没有新节点,间接触发旧节点的
destory
钩子 - 没有旧节点,阐明是页面刚开始初始化的时候,此时,基本不须要比拟了,间接全是新建,所以只调用
createElm
- 旧节点和新节点本身一样,通过
sameVnode
判断节点是否一样,一样时,间接调用patchVnode
去解决这两个节点 - 旧节点和新节点本身不一样,当两个节点不一样的时候,间接创立新节点,删除旧节点
上面次要讲的是patchVnode
局部
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { // 如果新旧节点统一,什么都不做 if (oldVnode === vnode) { return } // 让vnode.el援用到当初的实在dom,当el批改时,vnode.el会同步变动 const elm = vnode.elm = oldVnode.elm // 异步占位符 if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return } // 如果新旧都是动态节点,并且具备雷同的key // 当vnode是克隆节点或是v-once指令管制的节点时,只须要把oldVnode.elm和oldVnode.child都复制到vnode上 // 也不必再有其余操作 if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance return } let i const data = vnode.data if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) } const oldCh = oldVnode.children const ch = vnode.children if (isDef(data) && isPatchable(vnode)) { for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) } // 如果vnode不是文本节点或者正文节点 if (isUndef(vnode.text)) { // 并且都有子节点 if (isDef(oldCh) && isDef(ch)) { // 并且子节点不完全一致,则调用updateChildren if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) // 如果只有新的vnode有子节点 } else if (isDef(ch)) { if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') // elm曾经援用了老的dom节点,在老的dom节点上增加子节点 addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) // 如果新vnode没有子节点,而vnode有子节点,间接删除老的oldCh } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) // 如果老节点是文本节点 } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, '') } // 如果新vnode和老vnode是文本节点或正文节点 // 然而vnode.text != oldVnode.text时,只须要更新vnode.elm的文本内容就能够 } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } }
patchVnode
次要做了几个判断:
- 新节点是否是文本节点,如果是,则间接更新
dom
的文本内容为新节点的文本内容 - 新节点和旧节点如果都有子节点,则解决比拟更新子节点
- 只有新节点有子节点,旧节点没有,那么不必比拟了,所有节点都是全新的,所以间接全副新建就好了,新建是指创立出所有新
DOM
,并且增加进父节点 - 只有旧节点有子节点而新节点没有,阐明更新后的页面,旧节点全副都不见了,那么要做的,就是把所有的旧节点删除,也就是间接把
DOM
删除
子节点不完全一致,则调用updateChildren
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { let oldStartIdx = 0 // 旧头索引 let newStartIdx = 0 // 新头索引 let oldEndIdx = oldCh.length - 1 // 旧尾索引 let newEndIdx = newCh.length - 1 // 新尾索引 let oldStartVnode = oldCh[0] // oldVnode的第一个child let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最初一个child let newStartVnode = newCh[0] // newVnode的第一个child let newEndVnode = newCh[newEndIdx] // newVnode的最初一个child let oldKeyToIdx, idxInOld, vnodeToMove, refElm // removeOnly is a special flag used only by <transition-group> // to ensure removed elements stay in correct relative positions // during leaving transitions const canMove = !removeOnly // 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了,证实diff完了,循环完结 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { // 如果oldVnode的第一个child不存在 if (isUndef(oldStartVnode)) { // oldStart索引右移 oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left // 如果oldVnode的最初一个child不存在 } else if (isUndef(oldEndVnode)) { // oldEnd索引左移 oldEndVnode = oldCh[--oldEndIdx] // oldStartVnode和newStartVnode是同一个节点 } else if (sameVnode(oldStartVnode, newStartVnode)) { // patch oldStartVnode和newStartVnode, 索引左移,持续循环 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] // oldEndVnode和newEndVnode是同一个节点 } else if (sameVnode(oldEndVnode, newEndVnode)) { // patch oldEndVnode和newEndVnode,索引右移,持续循环 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] // oldStartVnode和newEndVnode是同一个节点 } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right // patch oldStartVnode和newEndVnode patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) // 如果removeOnly是false,则将oldStartVnode.eml挪动到oldEndVnode.elm之后 canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) // oldStart索引右移,newEnd索引左移 oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] // 如果oldEndVnode和newStartVnode是同一个节点 } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left // patch oldEndVnode和newStartVnode patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) // 如果removeOnly是false,则将oldEndVnode.elm挪动到oldStartVnode.elm之前 canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) // oldEnd索引左移,newStart索引右移 oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] // 如果都不匹配 } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 尝试在oldChildren中寻找和newStartVnode的具备雷同的key的Vnode idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 如果未找到,阐明newStartVnode是一个新的节点 if (isUndef(idxInOld)) { // New element // 创立一个新Vnode createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) // 如果找到了和newStartVnodej具备雷同的key的Vnode,叫vnodeToMove } else { vnodeToMove = oldCh[idxInOld] /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !vnodeToMove) { warn( 'It seems there are duplicate keys that is causing an update error. ' + 'Make sure each v-for item has a unique key.' ) } // 比拟两个具备雷同的key的新节点是否是同一个节点 //不设key,newCh和oldCh只会进行头尾两端的互相比拟,设key后,除了头尾两端的比拟外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key能够更高效的利用dom。 if (sameVnode(vnodeToMove, newStartVnode)) { // patch vnodeToMove和newStartVnode patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue) // 革除 oldCh[idxInOld] = undefined // 如果removeOnly是false,则将找到的和newStartVnodej具备雷同的key的Vnode,叫vnodeToMove.elm // 挪动到oldStartVnode.elm之前 canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) // 如果key雷同,然而节点不雷同,则创立一个新的节点 } else { // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) } } // 右移 newStartVnode = newCh[++newStartIdx] } }
while
循环次要解决了以下五种情景:
- 当新老
VNode
节点的start
雷同时,间接patchVnode
,同时新老VNode
节点的开始索引都加 1 - 当新老
VNode
节点的end
雷同时,同样间接patchVnode
,同时新老VNode
节点的完结索引都减 1 - 当老
VNode
节点的start
和新VNode
节点的end
雷同时,这时候在patchVnode
后,还须要将以后实在dom
节点挪动到oldEndVnode
的前面,同时老VNode
节点开始索引加 1,新VNode
节点的完结索引减 1 - 当老
VNode
节点的end
和新VNode
节点的start
雷同时,这时候在patchVnode
后,还须要将以后实在dom
节点挪动到oldStartVnode
的后面,同时老VNode
节点完结索引减 1,新VNode
节点的开始索引加 1 如果都不满足以上四种情景,那阐明没有雷同的节点能够复用,则会分为以下两种状况:
- 从旧的
VNode
为key
值,对应index
序列为value
值的哈希表中找到与newStartVnode
统一key
的旧的VNode
节点,再进行patchVnode
,同时将这个实在dom
挪动到oldStartVnode
对应的实在dom
的后面 - 调用
createElm
创立一个新的dom
节点放到以后newStartIdx
的地位
- 从旧的
小结
- 当数据产生扭转时,订阅者
watcher
就会调用patch
给实在的DOM
打补丁 - 通过
isSameVnode
进行判断,雷同则调用patchVnode
办法 patchVnode
做了以下操作:- 找到对应的实在
dom
,称为el
- 如果都有都有文本节点且不相等,将
el
文本节点设置为Vnode
的文本节点 - 如果
oldVnode
有子节点而VNode
没有,则删除el
子节点 - 如果
oldVnode
没有子节点而VNode
有,则将VNode
的子节点实在化后增加到el
- 如果两者都有子节点,则执行
updateChildren
函数比拟子节点
- 找到对应的实在
updateChildren
次要做了以下操作:- 设置新旧
VNode
的头尾指针 - 新旧头尾指针进行比拟,循环向两头聚拢,依据状况调用
patchVnode
进行patch
反复流程、调用createElem
创立一个新节点,从哈希表寻找key
统一的VNode
节点再分状况操作
- 设置新旧