Vue-router 路由模式有几种

vue-router3 种路由模式:hashhistoryabstract,对应的源码如下所示

switch (mode) {    case 'history':    this.history = new HTML5History(this, options.base)    break    case 'hash':    this.history = new HashHistory(this, options.base, this.fallback)      break    case 'abstract':    this.history = new AbstractHistory(this, options.base)      break  default:    if (process.env.NODE_ENV !== 'production') {      assert(false, `invalid mode: ${mode}`)    }}

其中,3 种路由模式的阐明如下:

  • hash: 应用 URL hash 值来作路由,反对所有浏览器
  • history : 依赖 HTML5 History API 和服务器配置
  • abstract : 反对所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会主动强制进入这个模式.

为什么 Vuex 的 mutation 中不能做异步操作?

  • Vuex中所有的状态更新的惟一路径都是mutation,异步操作通过 Action 来提交 mutation实现,这样能够不便地跟踪每一个状态的变动,从而可能实现一些工具帮忙更好地理解咱们的利用。
  • 每个mutation执行实现后都会对应到一个新的状态变更,这样devtools就能够打个快照存下来,而后就能够实现 time-travel 了。如果mutation反对异步操作,就没有方法晓得状态是何时更新的,无奈很好的进行状态的追踪,给调试带来艰难。

Vue组件之间通信形式有哪些

Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。 Vue 组件间通信只有指以下 3 类通信父子组件通信隔代组件通信兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信

组件传参的各种形式

组件通信罕用形式有以下几种

  • props / $emit 实用 父子组件通信

    • 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
  • ref$parent / $children(vue3废除) 实用 父子组件通信

    • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
    • $parent / $children:拜访拜访父组件的属性或办法 / 拜访子组件的属性或办法
  • EventBus ($emit / $on) 实用于 父子、隔代、兄弟组件通信

    • 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件
  • $attrs / $listeners(vue3废除) 实用于 隔代组件通信

    • $attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 ( classstyle 除外 )。当一个组件没有申明任何 prop时,这里会蕴含所有父作用域的绑定 ( classstyle 除外 ),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用
    • $listeners:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on="$listeners" 传入外部组件
  • provide / inject 实用于 隔代组件通信

    • 先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。 provide / inject API 次要解决了跨级组件间的通信问题, 不过它的应用场景,次要是子组件获取下级组件的状态 ,跨级组件间建设了一种被动提供与依赖注入的关系
  • $root 实用于 隔代组件通信 拜访根组件中的属性或办法,是根组件,不是父组件。$root只对根组件有用
  • Vuex 实用于 父子、隔代、兄弟组件通信

    • Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store” 基本上就是一个容器,它蕴含着你的利用中大部分的状态 ( state )
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
    • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。

依据组件之间关系探讨组件通信最为清晰无效

  • 父子组件:props/$emit/$parent/ref
  • 兄弟组件:$parent/eventbus/vuex
  • 跨层级关系:eventbus/vuex/provide+inject/$attrs + $listeners/$root
上面演示组件之间通信三种状况: 父传子、子传父、兄弟组件之间的通信

1. 父子组件通信

应用props,父组件能够应用props向子组件传递数据。

父组件vue模板father.vue:

<template>  <child :msg="message"></child></template><script>import child from './child.vue';export default {  components: {    child  },  data () {    return {      message: 'father message';    }  }}</script>

子组件vue模板child.vue:

<template>    <div>{{msg}}</div></template><script>export default {  props: {    msg: {      type: String,      required: true    }  }}</script>

回调函数(callBack)

父传子:将父组件里定义的method作为props传入子组件

// 父组件Parent.vue:<Child :changeMsgFn="changeMessage">methods: {    changeMessage(){        this.message = 'test'    }}
// 子组件Child.vue:<button @click="changeMsgFn">props:['changeMsgFn']

子组件向父组件通信

父组件向子组件传递事件办法,子组件通过$emit触发事件,回调给父组件

父组件vue模板father.vue:

<template>    <child @msgFunc="func"></child></template><script>import child from './child.vue';export default {    components: {        child    },    methods: {        func (msg) {            console.log(msg);        }    }}</script>

子组件vue模板child.vue:

<template>    <button @click="handleClick">点我</button></template><script>export default {    props: {        msg: {            type: String,            required: true        }    },    methods () {        handleClick () {          //........          this.$emit('msgFunc');        }    }}</script>

2. provide / inject 跨级拜访先人组件的数据

父组件通过应用provide(){return{}}提供须要传递的数据

export default {  data() {    return {      title: '我是父组件',      name: 'poetry'    }  },  methods: {    say() {      alert(1)    }  },  // provide属性 可能为前面的后辈组件/嵌套的组件提供所须要的变量和办法  provide() {    return {      message: '我是先人组件提供的数据',      name: this.name, // 传递属性      say: this.say    }  }}

子组件通过应用inject:[“参数1”,”参数2”,…]接管父组件传递的参数

<template>  <p>曾孙组件</p>  <p>{{message}}</p></template><script>export default {  // inject 注入/接管先人组件传递的所须要的数据即可   //接管到的数据 变量 跟data外面的变量一样 能够间接绑定到页面 {{}}  inject: [ "message","say"],  mounted() {    this.say();  },};</script>

3. $parent + $children 获取父组件实例和子组件实例的汇合

  • this.$parent 能够间接拜访该组件的父实例或组件
  • 父组件也能够通过 this.$children 拜访它所有的子组件;须要留神 $children 并不保障程序,也不是响应式的
<!-- parent.vue --><template><div>  <child1></child1>     <child2></child2>   <button @click="clickChild">$children形式获取子组件值</button></div></template><script>import child1 from './child1'import child2 from './child2'export default {  data(){    return {      total: 108    }  },  components: {    child1,    child2    },  methods: {    funa(e){      console.log("index",e)    },    clickChild(){      console.log(this.$children[0].msg);      console.log(this.$children[1].msg);    }  }}</script>
<!-- child1.vue --><template>  <div>    <button @click="parentClick">点击拜访父组件</button>  </div></template><script>export default {  data(){    return {      msg:"child1"    }  },  methods: {    // 拜访父组件数据    parentClick(){      this.$parent.funa("xx")      console.log(this.$parent.total);    }  }}</script>
<!-- child2.vue --><template>  <div>    child2  </div></template><script>export default {  data(){    return {     msg: 'child2'    }  }}</script>

4. $attrs + $listeners多级组件通信

$attrs 蕴含了从父组件传过来的所有props属性
// 父组件Parent.vue:<Child :name="name" :age="age"/>// 子组件Child.vue:<GrandChild v-bind="$attrs" />// 孙子组件GrandChild<p>姓名:{{$attrs.name}}</p><p>年龄:{{$attrs.age}}</p>
$listeners蕴含了父组件监听的所有事件
// 父组件Parent.vue:<Child :name="name" :age="age" @changeNameFn="changeName"/>// 子组件Child.vue:<button @click="$listeners.changeNameFn"></button>

5. ref 父子组件通信

// 父组件Parent.vue:<Child ref="childComp"/><button @click="changeName"></button>changeName(){    console.log(this.$refs.childComp.age);    this.$refs.childComp.changeAge()}// 子组件Child.vue:data(){    return{        age:20    }},methods(){    changeAge(){        this.age=15  }}

6. 非父子, 兄弟组件之间通信

vue2中废除了broadcast播送和散发事件的办法。父子组件中能够用props$emit()。如何实现非父子组件间的通信,能够通过实例一个vue实例Bus作为媒介,要互相通信的兄弟组件之中,都引入Bus,而后通过别离调用Bus事件触发和监听来实现通信和参数传递。Bus.js能够是这样:
// Bus.js// 创立一个地方工夫总线类  class Bus {    constructor() {      this.callbacks = {};   // 寄存事件的名字    }    $on(name, fn) {      this.callbacks[name] = this.callbacks[name] || [];      this.callbacks[name].push(fn);    }    $emit(name, args) {      if (this.callbacks[name]) {        this.callbacks[name].forEach((cb) => cb(args));      }    }  }  // main.js  Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  // 另一种形式  Vue.prototype.$bus = new Vue() // Vue曾经实现了Bus的性能  
<template>    <button @click="toBus">子组件传给兄弟组件</button></template><script>export default{    methods: {    toBus () {      this.$bus.$emit('foo', '来自兄弟组件')    }  }}</script>

另一个组件也在钩子函数中监听on事件

export default {  data() {    return {      message: ''    }  },  mounted() {    this.$bus.$on('foo', (msg) => {      this.message = msg    })  }}

7. $root 拜访根组件中的属性或办法

  • 作用:拜访根组件中的属性或办法
  • 留神:是根组件,不是父组件。$root只对根组件有用
var vm = new Vue({  el: "#app",  data() {    return {      rootInfo:"我是根元素的属性"    }  },  methods: {    alerts() {      alert(111)    }  },  components: {    com1: {      data() {        return {          info: "组件1"        }      },      template: "<p>{{ info }} <com2></com2></p>",      components: {        com2: {          template: "<p>我是组件1的子组件</p>",          created() {            this.$root.alerts()// 根组件办法            console.log(this.$root.rootInfo)// 我是根元素的属性          }        }      }    }  }});

8. vuex

  • 实用场景: 简单关系的组件数据传递
  • Vuex作用相当于一个用来存储共享变量的容器

  • state用来寄存共享变量的中央
  • getter,能够减少一个getter派生状态,(相当于store中的计算属性),用来取得共享变量的值
  • mutations用来寄存批改state的办法。
  • actions也是用来寄存批改state的办法,不过action是在mutations的根底上进行。罕用来做一些异步操作

小结

  • 父子关系的组件数据传递抉择 props$emit进行传递,也可抉择ref
  • 兄弟关系的组件数据传递可抉择$bus,其次能够抉择$parent进行传递
  • 先人与后辈组件数据传递可抉择attrslisteners或者 ProvideInject
  • 简单关系的组件数据传递能够通过vuex寄存共享的变量

Vue中组件和插件有什么区别

1. 组件是什么

组件就是把图形、非图形的各种逻辑均形象为一个对立的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都能够视为一个组件

组件的劣势

  • 升高整个零碎的耦合度,在放弃接口不变的状况下,咱们能够替换不同的组件疾速实现需要,例如输入框,能够替换为日历、工夫、范畴等组件作具体的实现
  • 调试不便,因为整个零碎是通过组件组合起来的,在呈现问题的时候,能够用排除法间接移除组件,或者依据报错的组件疾速定位问题,之所以可能疾速定位,是因为每个组件之间低耦合,职责繁多,所以逻辑会比剖析整个零碎要简略
  • 进步可维护性,因为每个组件的职责繁多,并且组件在零碎中是被复用的,所以对代码进行优化可取得零碎的整体降级

2. 插件是什么

插件通常用来为 Vue 增加全局性能。插件的性能范畴没有严格的限度——个别有上面几种:

  • 增加全局办法或者属性。如: vue-custom-element
  • 增加全局资源:指令/过滤器/过渡等。如 vue-touch
  • 通过全局混入来增加一些组件选项。如vue-router
  • 增加 Vue 实例办法,通过把它们增加到 Vue.prototype 上实现。
  • 一个库,提供本人的 API,同时提供下面提到的一个或多个性能。如vue-router

3. 两者的区别

两者的区别次要体现在以下几个方面:

  • 编写模式
  • 注册模式
  • 应用场景

3.1 编写模式

编写组件

编写一个组件,能够有很多形式,咱们最常见的就是vue单文件的这种格局,每一个.vue文件咱们都能够看成是一个组件

vue文件规范格局

<template></template><script>export default{     ...}</script><style></style>

咱们还能够通过template属性来编写一个组件,如果组件内容多,咱们能够在内部定义template组件内容,如果组件内容并不多,咱们可间接写在template属性上

<template id="testComponent">     // 组件显示的内容    <div>component!</div>   </template>Vue.component('componentA',{     template: '#testComponent'      template: `<div>component</div>`  // 组件内容少能够通过这种模式})

编写插件

vue插件的实现应该裸露一个 install 办法。这个办法的第一个参数是 Vue 结构器,第二个参数是一个可选的选项对象

MyPlugin.install = function (Vue, options) {  // 1. 增加全局办法或 property  Vue.myGlobalMethod = function () {    // 逻辑...  }  // 2. 增加全局资源  Vue.directive('my-directive', {    bind (el, binding, vnode, oldVnode) {      // 逻辑...    }    ...  })  // 3. 注入组件选项  Vue.mixin({    created: function () {      // 逻辑...    }    ...  })  // 4. 增加实例办法  Vue.prototype.$myMethod = function (methodOptions) {    // 逻辑...  }}

3.2 注册模式

组件注册

vue组件注册次要分为全局注册部分注册

全局注册通过Vue.component办法,第一个参数为组件的名称,第二个参数为传入的配置项

Vue.component('my-component-name', { /* ... */ })

部分注册只需在用到的中央通过components属性注册一个组件

const component1 = {...} // 定义一个组件export default {    components:{        component1   // 部分注册    }}

插件注册

插件的注册通过Vue.use()的形式进行注册(装置),第一个参数为插件的名字,第二个参数是可抉择的配置项

Vue.use(插件名字,{ /* ... */} )

留神的是:

注册插件的时候,须要在调用 new Vue() 启动利用之前实现

Vue.use会主动阻止屡次注册雷同插件,只会注册一次

4. 应用场景

  • 组件 (Component) 是用来形成你的 App 的业务模块,它的指标是 App.vue
  • 插件 (Plugin) 是用来加强你的技术栈的功能模块,它的指标是 Vue 自身

简略来说,插件就是指对Vue的性能的加强或补充

Watch中的deep:true是如何实现的

当用户指定了 watch 中的deep属性为 true 时,如果以后监控的值是数组类型。会对对象中的每一项进行求值,此时会将以后 watcher存入到对应属性的依赖中,这样数组中对象发生变化时也会告诉数据更新

源码相干

get () {     pushTarget(this) // 先将以后依赖放到 Dep.target上     let value     const vm = this.vm     try {         value = this.getter.call(vm, vm)     } catch (e) {         if (this.user) {             handleError(e, vm, `getter for watcher "${this.expression}"`)         } else {             throw e         }     } finally {         if (this.deep) { // 如果须要深度监控         traverse(value) // 会对对象中的每一项取值,取值时会执行对应的get办法     }popTarget() }

Vue中diff算法原理

DOM操作是十分低廉的,因而咱们须要尽量地缩小DOM操作。这就须要找出本次DOM必须更新的节点来更新,其余的不更新,这个找出的过程,就须要利用diff算法

vuediff算法是平级比拟,不思考跨级比拟的状况。外部采纳深度递归的形式+双指针(头尾都加指针)的形式进行比拟。

简略来说,Diff算法有以下过程

  • 同级比拟,再比拟子节点(依据keytag标签名判断)
  • 先判断一方有子节点和一方没有子节点的状况(如果新的children没有子节点,将旧的子节点移除)
  • 比拟都有子节点的状况(外围diff)
  • 递归比拟子节点
  • 失常Diff两个树的工夫复杂度是O(n^3),但理论状况下咱们很少会进行跨层级的挪动DOM,所以VueDiff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才须要用外围的Diff算法进行同层级比拟。
  • Vue2的外围Diff算法采纳了双端比拟的算法,同时从新旧children的两端开始进行比拟,借助key值找到可复用的节点,再进行相干操作。相比ReactDiff算法,同样状况下能够缩小挪动节点次数,缩小不必要的性能损耗,更加的优雅
  • 在创立VNode时就确定其类型,以及在mount/patch的过程中采纳位运算来判断一个VNode的类型,在这个根底之上再配合外围的Diff算法,使得性能上较Vue2.x有了晋升

vue3中采纳最长递增子序列来实现diff优化

答复范例

思路

  • diff算法是干什么的
  • 它的必要性
  • 它何时执行
  • 具体执行形式
  • 拔高:说一下vue3中的优化

答复范例

  1. Vue中的diff算法称为patching算法,它由Snabbdom批改而来,虚构DOM要想转化为实在DOM就须要通过patch办法转换
  2. 最后Vue1.x视图中每个依赖均有更新函数对应,能够做到精准更新,因而并不需要虚构DOMpatching算法反对,然而这样粒度过细导致Vue1.x无奈承载较大利用;Vue 2.x中为了升高Watcher粒度,每个组件只有一个Watcher与之对应,此时就须要引入patching算法能力准确找到发生变化的中央并高效更新
  3. vuediff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数取得最新的虚构DOM,而后执行patch函数,并传入新旧两次虚构DOM,通过比对两者找到变动的中央,最初将其转化为对应的DOM操作
  4. patch过程是一个递归过程,遵循深度优先、同层比拟的策略;以vue3patch为例
  5. 首先判断两个节点是否为雷同同类节点,不同则删除从新创立
  6. 如果单方都是文本则更新文本内容
  7. 如果单方都是元素节点则递归更新子元素,同时更新元素属性
  8. 更新子节点时又分了几种状况

    • 新的子节点是文本,老的子节点是数组则清空,并设置文本;
    • 新的子节点是文本,老的子节点是文本则间接更新文本;
    • 新的子节点是数组,老的子节点是文本则清空文本,并创立新子节点数组中的子元素;
    • 新的子节点是数组,老的子节点也是数组,那么比拟两组子节点,更新细节blabla
  9. vue3中引入的更新策略:动态节点标记等

vdom中diff算法的繁难实现

以下代码只是帮忙大家了解diff算法的原理和流程

  1. vdom转化为实在dom
const createElement = (vnode) => {  let tag = vnode.tag;  let attrs = vnode.attrs || {};  let children = vnode.children || [];  if(!tag) {    return null;  }  //创立元素  let elem = document.createElement(tag);  //属性  let attrName;  for (attrName in attrs) {    if(attrs.hasOwnProperty(attrName)) {      elem.setAttribute(attrName, attrs[attrName]);    }  }  //子元素  children.forEach(childVnode => {    //给elem增加子元素    elem.appendChild(createElement(childVnode));  })  //返回实在的dom元素  return elem;}
  1. 用繁难diff算法做更新操作
function updateChildren(vnode, newVnode) {  let children = vnode.children || [];  let newChildren = newVnode.children || [];  children.forEach((childVnode, index) => {    let newChildVNode = newChildren[index];    if(childVnode.tag === newChildVNode.tag) {      //深层次比照, 递归过程      updateChildren(childVnode, newChildVNode);    } else {      //替换      replaceNode(childVnode, newChildVNode);    }  })}

</details>

参考 前端进阶面试题具体解答

为什么要应用异步组件

  1. 节俭打包出的后果,异步组件离开打包,采纳jsonp的形式进行加载,无效解决文件过大的问题。
  2. 外围就是包组件定义变成一个函数,依赖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 }

从0到1本人构架一个vue我的项目,说说有哪些步骤、哪些重要插件、目录构造你会怎么组织

综合实际类题目,考查实战能力。没有什么相对的正确答案,把平时工作的重点有条理的形容一下即可

思路

  • 构建我的项目,创立我的项目根本构造
  • 引入必要的插件:
  • 代码标准:prettiereslint
  • 提交标准:husky,lint-staged`
  • 其余罕用:svg-loadervueusenprogress
  • 常见目录构造

答复范例

  1. 0创立一个我的项目我大抵会做以下事件:我的项目构建、引入必要插件、代码标准、提交标准、罕用库和组件
  2. 目前vue3我的项目我会用vite或者create-vue创立我的项目
  3. 接下来引入必要插件:路由插件vue-router、状态治理vuex/piniaui库我比拟喜爱element-plus和antd-vuehttp工具我会选axios
  4. 其余比拟罕用的库有vueusenprogress,图标能够应用vite-svg-loader
  5. 上面是代码标准:联合prettiereslint即可
  6. 最初是提交标准,能够应用huskylint-stagedcommitlint
  7. 目录构造我有如下习惯: .vscode:用来放我的项目中的 vscode 配置
  8. plugins:用来放 vite 插件的 plugin 配置
  9. public:用来放一些诸如 页头icon 之类的公共文件,会被打包到dist根目录下
  10. src:用来放我的项目代码文件
  11. api:用来放http的一些接口配置
  12. assets:用来放一些 CSS 之类的动态资源
  13. components:用来放我的项目通用组件
  14. layout:用来放我的项目的布局
  15. router:用来放我的项目的路由配置
  16. store:用来放状态治理Pinia的配置
  17. utils:用来放我的项目中的工具办法类
  18. views:用来放我的项目的页面文件

keep-alive 应用场景和原理

  • keep-aliveVue 内置的一个组件, 能够实现组件缓存 ,当组件切换时不会对以后组件进行卸载。 个别联合路由和动静组件一起应用 ,用于缓存组件
  • 提供 includeexclude 属性, 容许组件有条件的进行缓存 。两者都反对字符串或正则表达式,include 示意只有名称匹配的组件会被缓存,exclude 示意任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include
  • 对应两个钩子函数 activateddeactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated
  • keep-alive 的中还使用了 LRU(最近起码应用) 算法,抉择最近最久未应用的组件予以淘汰
  • <keep-alive></keep-alive> 包裹动静组件时,会缓存不流动的组件实例,次要用于保留组件状态或防止从新渲染
  • 比方有一个列表和一个详情,那么用户就会常常执行关上详情=>返回列表=>关上详情…这样的话列表和详情都是一个频率很高的页面,那么就能够对列表组件应用<keep-alive></keep-alive>进行缓存,这样用户每次返回列表的时候,都能从缓存中疾速渲染,而不是从新渲染

对于keep-alive的根本用法

<keep-alive>  <component :is="view"></component></keep-alive>

应用includesexclude

<keep-alive include="a,b">  <component :is="view"></component></keep-alive><!-- 正则表达式 (应用 `v-bind`) --><keep-alive :include="/a|b/">  <component :is="view"></component></keep-alive><!-- 数组 (应用 `v-bind`) --><keep-alive :include="['a', 'b']">  <component :is="view"></component></keep-alive>

匹配首先查看组件本身的 name 选项,如果 name 选项不可用,则匹配它的部分注册名称 (父组件 components 选项的键值),匿名组件不能被匹配

设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activateddeactivated):

  • 首次进入组件时:beforeRouteEnter > beforeCreate > created> mounted > activated > ... ... > beforeRouteLeave > deactivated
  • 再次进入组件时:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated

应用场景

应用准则:当咱们在某些场景下不须要让页面从新加载时咱们能够应用keepalive

举个栗子:

当咱们从首页–>列表页–>商详页–>再返回,这时候列表页应该是须要keep-alive

首页–>列表页–>商详页–>返回到列表页(须要缓存)–>返回到首页(须要缓存)–>再次进入列表页(不须要缓存),这时候能够按需来管制页面的keep-alive

在路由中设置keepAlive属性判断是否须要缓存

{  path: 'list',  name: 'itemList', // 列表页  component (resolve) {    require(['@/pages/item/list'], resolve) }, meta: {  keepAlive: true,  title: '列表页' }}

应用<keep-alive>

<div id="app" class='wrapper'>    <keep-alive>        <!-- 须要缓存的视图组件 -->         <router-view v-if="$route.meta.keepAlive"></router-view>     </keep-alive>      <!-- 不须要缓存的视图组件 -->     <router-view v-if="!$route.meta.keepAlive"></router-view></div>

思考题:缓存后如何获取数据

解决方案能够有以下两种:

  • beforeRouteEnter:每次组件渲染的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){    next(vm=>{        console.log(vm)        // 每次进入路由执行        vm.getData()  // 获取数据    })},
  • actived:在keep-alive缓存的组件被激活的时候,都会执行actived钩子
// 留神:服务器端渲染期间avtived不被调用activated(){  this.getData() // 获取数据},

扩大补充:LRU 算法是什么?

LRU 的核心思想是如果数据最近被拜访过,那么未来被拜访的几率也更高,所以咱们将命中缓存的组件 key 从新插入到 this.keys 的尾部,这样一来,this.keys 中越往头部的数据行将来被拜访几率越低,所以当缓存数量达到最大值时,咱们就删除未来被拜访几率最低的数据,即 this.keys 中第一个缓存的组件

相干代码

keep-alivevue中内置的一个组件

源码地位:src/core/components/keep-alive.js

export default {  name: "keep-alive",  abstract: true, //形象组件  props: {    include: patternTypes, //要缓存的组件    exclude: patternTypes, //要排除的组件    max: [String, Number], //最大缓存数  },  created() {    this.cache = Object.create(null); //缓存对象  {a:vNode,b:vNode}    this.keys = []; //缓存组件的key汇合 [a,b]  },  destroyed() {    for (const key in this.cache) {      pruneCacheEntry(this.cache, key, this.keys);    }  },  mounted() {    //动静监听include  exclude    this.$watch("include", (val) => {      pruneCache(this, (name) => matches(val, name));    });    this.$watch("exclude", (val) => {      pruneCache(this, (name) => !matches(val, name));    });  },  render() {    const slot = this.$slots.default; //获取包裹的插槽默认值 获取默认插槽中的第一个组件节点    const vnode: VNode = getFirstComponentChild(slot); //获取第一个子组件    // 获取该组件节点的componentOptions    const componentOptions: ?VNodeComponentOptions =      vnode && vnode.componentOptions;    if (componentOptions) {      // 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag      const name: ?string = getComponentName(componentOptions);      const { include, exclude } = this;      // 不走缓存 如果name不在inlcude中或者存在于exlude中则示意不缓存,间接返回vnode      if (        // not included  不蕴含        (include && (!name || !matches(include, name))) ||        // excluded  排除外面        (exclude && name && matches(exclude, name))      ) {        //返回虚构节点        return vnode;      }      const { cache, keys } = this;      // 获取组件的key值      const key: ?string =        vnode.key == null          ? // same constructor may get registered as different local components            // so cid alone is not enough (#3269)            componentOptions.Ctor.cid +            (componentOptions.tag ? `::${componentOptions.tag}` : "")          : vnode.key;      // 拿到key值后去this.cache对象中去寻找是否有该值,如果有则示意该组件有缓存,即命中缓存      if (cache[key]) {        //通过key 找到缓存 获取实例        vnode.componentInstance = cache[key].componentInstance;        // make current key freshest        remove(keys, key); //通过LRU算法把数组外面的key删掉        keys.push(key); //把它放在数组开端      } else {        cache[key] = vnode; //没找到就换存下来        keys.push(key); //把它放在数组开端        // prune oldest entry  //如果超过最大值就把数组第0项删掉        if (this.max && keys.length > parseInt(this.max)) {          pruneCacheEntry(cache, keys[0], keys, this._vnode);        }      }      vnode.data.keepAlive = true; //标记虚构节点曾经被缓存    }    // 返回虚构节点    return vnode || (slot && slot[0]);  },};

能够看到该组件没有template,而是用了render,在组件渲染的时候会主动执行render函数

this.cache是一个对象,用来存储须要缓存的组件,它将以如下模式存储:

this.cache = {  'key1':'组件1',  'key2':'组件2',  // ...}

在组件销毁的时候执行pruneCacheEntry函数

function pruneCacheEntry (  cache: VNodeCache,  key: string,  keys: Array<string>,  current?: VNode) {  const cached = cache[key]  /* 判断以后没有处于被渲染状态的组件,将其销毁*/  if (cached && (!current || cached.tag !== current.tag)) {    cached.componentInstance.$destroy()  }  cache[key] = null  remove(keys, key)}

mounted钩子函数中观测 includeexclude 的变动,如下:

mounted () {  this.$watch('include', val => {      pruneCache(this, name => matches(val, name))  })  this.$watch('exclude', val => {      pruneCache(this, name => !matches(val, name))  })}

如果includeexclude 产生了变动,即示意定义须要缓存的组件的规定或者不须要缓存的组件的规定产生了变动,那么就执行pruneCache函数,函数如下

function pruneCache (keepAliveInstance, filter) {  const { cache, keys, _vnode } = keepAliveInstance  for (const key in cache) {    const cachedNode = cache[key]    if (cachedNode) {      const name = getComponentName(cachedNode.componentOptions)      if (name && !filter(name)) {        pruneCacheEntry(cache, key, keys, _vnode)      }    }  }}

在该函数内对this.cache对象进行遍历,取出每一项的name值,用其与新的缓存规定进行匹配,如果匹配不上,则示意在新的缓存规定下该组件曾经不须要被缓存,则调用pruneCacheEntry函数将其从this.cache对象剔除即可

对于keep-alive的最弱小缓存性能是在render函数中实现

首先获取组件的key值:

const key = vnode.key == null? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : ''): vnode.key

拿到key值后去this.cache对象中去寻找是否有该值,如果有则示意该组件有缓存,即命中缓存,如下:

/* 如果命中缓存,则间接从缓存中拿 vnode 的组件实例 */if (cache[key]) {    vnode.componentInstance = cache[key].componentInstance    /* 调整该组件key的程序,将其从原来的中央删掉并从新放在最初一个 */    remove(keys, key)    keys.push(key)} 

间接从缓存中拿 vnode 的组件实例,此时从新调整该组件key的程序,将其从原来的中央删掉并从新放在this.keys中最初一个

this.cache对象中没有该key值的状况,如下:

/* 如果没有命中缓存,则将其设置进缓存 */else {    cache[key] = vnode    keys.push(key)    /* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */    if (this.max && keys.length > parseInt(this.max)) {        pruneCacheEntry(cache, keys[0], keys, this._vnode)    }}

表明该组件还没有被缓存过,则以该组件的key为键,组件vnode为值,将其存入this.cache中,并且把key存入this.keys

此时再判断this.keys中缓存组件的数量是否超过了设置的最大缓存数量值this.max,如果超过了,则把第一个缓存组件删掉

vue-router 动静路由是什么

咱们常常须要把某种模式匹配到的所有路由,全都映射到同个组件。例如,咱们有一个 User 组件,对于所有 ID 各不相同的用户,都要应用这个组件来渲染。那么,咱们能够在 vue-router 的路由门路中应用“动静门路参数”(dynamic segment) 来达到这个成果
const User = {  template: "<div>User</div>",};const router = new VueRouter({  routes: [    // 动静门路参数 以冒号结尾    { path: "/user/:id", component: User },  ],});

问题: vue-router 组件复用导致路由参数生效怎么办?

解决办法:

  1. 通过 watch 监听路由参数再发申请
watch: { //通过watch来监听路由变动 "$route": function(){  this.getData(this.$route.params.xxx); }}
  1. :key 来阻止“复用”
<router-view :key="$route.fullPath" />

答复范例

  1. 很多时候,咱们须要将给定匹配模式的路由映射到同一个组件,这种状况就须要定义动静路由
  2. 例如,咱们可能有一个 User 组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router中,咱们能够在门路中应用一个动静字段来实现,例如:{ path: '/users/:id', component: User },其中:id就是门路参数
  3. 门路参数 用冒号 : 示意。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的模式裸露进去。
  4. 参数还能够有多个,例如/users/:username/posts/:postId;除了 $route.params 之外,$route 对象还公开了其余有用的信息,如 $route.query$route.hash

diff算法

工夫复杂度: 个树的齐全 diff 算法是一个工夫复杂度为 O(n*3) ,vue进行优化转化成 O(n)

了解:

  • 最小量更新, key 很重要。这个能够是这个节点的惟一标识,通知 diff 算法,在更改前后它们是同一个DOM节点

    • 扩大 v-for 为什么要有 key ,没有 key 会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改DOM),加 key 只会挪动缩小操作DOM。
  • 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。
  • 只进行同层比拟,不会进行跨层比拟。

diff算法的优化策略:四种命中查找,四个指针

  1. 旧前与新前(先比结尾,后插入和删除节点的这种状况)
  2. 旧后与新后(比结尾,前插入或删除的状况)
  3. 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)
  4. 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)

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'  }}

Vue template 到 render 的过程

vue的模版编译过程次要如下:template -> ast -> render函数

vue 在模版编译版本的码中会执行 compileToFunctions 将template转化为render函数:

// 将模板编译为render函数const { render, staticRenderFns } = compileToFunctions(template,options//省略}, this)

CompileToFunctions中的次要逻辑如下∶ (1)调用parse办法将template转化为ast(形象语法树)

constast = parse(template.trim(), options)
  • parse的指标:把tamplate转换为AST树,它是一种用 JavaScript对象的模式来形容整个模板。
  • 解析过程:利用正则表达式程序解析模板,当解析到开始标签、闭合标签、文本的时候都会别离执行对应的 回调函数,来达到结构AST树的目标。

AST元素节点总共三种类型:type为1示意一般元素、2为表达式、3为纯文本

(2)对动态节点做优化

optimize(ast,options)

这个过程次要剖析出哪些是动态节点,给其打一个标记,为后续更新渲染能够间接跳过动态节点做优化

深度遍历AST,查看每个子树的节点元素是否为动态节点或者动态节点根。如果为动态节点,他们生成的DOM永远不会扭转,这对运行时模板更新起到了极大的优化作用。

(3)生成代码

const code = generate(ast, options)

generate将ast形象语法树编译成 render字符串并将动态局部放到 staticRenderFns 中,最初通过 new Function(` render`) 生成render函数。

什么是 mixin ?

  • Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。
  • 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、 办法等,则能够将其编写为 mixin,并在组件中简略的援用它。
  • 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

Vue2.x 响应式数据原理

整体思路是数据劫持+观察者模式

对象外部通过 defineReactive 办法,应用 Object.defineProperty 来劫持各个属性的 settergetter(只会劫持曾经存在的属性),数组则是通过重写数组7个办法来实现。当页面应用对应属性时,每个属性都领有本人的 dep 属性,寄存他所依赖的 watcher(依赖收集),当属性变动后会告诉本人对应的 watcher 去更新(派发更新)

Object.defineProperty根本应用

function observer(value) { // proxy reflect    if (typeof value === 'object' && typeof value !== null)    for (let key in value) {        defineReactive(value, key, value[key]);    }}function defineReactive(obj, key, value) {    observer(value);    Object.defineProperty(obj, key, {        get() { // 收集对应的key 在哪个办法(组件)中被应用            return value;        },        set(newValue) {            if (newValue !== value) {                observer(newValue);                value = newValue; // 让key对应的办法(组件从新渲染)从新执行            }        }    })}let obj1 = { school: { name: 'poetry', age: 20 } };observer(obj1);console.log(obj1)

源码剖析

class Observer {  // 观测值  constructor(value) {    this.walk(value);  }  walk(data) {    // 对象上的所有属性顺次进行观测    let keys = Object.keys(data);    for (let i = 0; i < keys.length; i++) {      let key = keys[i];      let value = data[key];      defineReactive(data, key, value);    }  }}// Object.defineProperty数据劫持外围 兼容性在ie9以及以上function defineReactive(data, key, value) {  observe(value); // 递归要害  // --如果value还是一个对象会持续走一遍odefineReactive 层层遍历始终到value不是对象才进行  //   思考?如果Vue数据嵌套层级过深 >>性能会受影响  Object.defineProperty(data, key, {    get() {      console.log("获取值");      //须要做依赖收集过程 这里代码没写进去      return value;    },    set(newValue) {      if (newValue === value) return;      console.log("设置值");      //须要做派发更新过程 这里代码没写进去      value = newValue;    },  });}export function observe(value) {  // 如果传过来的是对象或者数组 进行属性劫持  if (    Object.prototype.toString.call(value) === "[object Object]" ||    Array.isArray(value)  ) {    return new Observer(value);  }}

说一说你对vue响应式了解答复范例

  • 所谓数据响应式就是可能使数据变动能够被检测并对这种变动做出响应的机制
  • MVVM框架中要解决的一个外围问题是连贯数据层和视图层,通过数据驱动利用,数据变动,视图更新,要做到这点的就须要对数据做响应式解决,这样一旦数据发生变化就能够立刻做出更新解决
  • vue为例阐明,通过数据响应式加上虚构DOMpatch算法,开发人员只须要操作数据,关怀业务,齐全不必接触繁琐的DOM操作,从而大大晋升开发效率,升高开发难度
  • vue2中的数据响应式会依据数据类型来做不同解决,如果是 对象则采纳Object.defineProperty()的形式定义数据拦挡,当数据被拜访或发生变化时,咱们感知并作出响应;如果是数组则通过笼罩数组对象原型的7个变更办法 ,使这些办法能够额定的做更新告诉,从而作出响应。这种机制很好的解决了数据响应化的问题,但在理论应用中也存在一些毛病:比方初始化时的递归遍历会造成性能损失;新增或删除属性时须要用户应用Vue.set/delete这样非凡的api能力失效;对于es6中新产生的MapSet这些数据结构不反对等问题
  • 为了解决这些问题,vue3从新编写了这一部分的实现:利用ES6Proxy代理要响应化的数据,它有很多益处,编程体验是统一的,不须要应用非凡api,初始化性能和内存耗费都失去了大幅改善;另外因为响应化的实现代码抽取为独立的reactivity包,使得咱们能够更灵便的应用它,第三方的扩大开发起来更加灵便了

vue和react的区别

=> 相同点:

1. 数据驱动页面,提供响应式的试图组件2. 都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents标准3. 数据流动单向,都反对服务器的渲染SSR4. 都有反对native的办法,react有React native, vue有wexx

=> 不同点:

 1.数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的 2.数据渲染:大规模的数据渲染,react更快 3.应用场景:React配合Redux架构适宜大规模多人合作简单我的项目,Vue适宜小快的我的项目 4.开发格调:react举荐做法jsx + inline style把html和css都写在js了      vue是采纳webpack + vue-loader单文件组件格局,html, js, css同一个文件

Vue-router 路由有哪些模式?

个别有两种模式:

 (1)**hash 模式**:前面的 hash 值的变动,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变动会触发 hashchange 事件。 (2)**history 模式**:利用了 HTML5 中新增的 pushState() 和 replaceState() 办法。这两个办法利用于浏览器的历史记录栈,在以后已有的 back、forward、go 的根底之上,它们提供了对历史记录进行批改的性能。只是当它们执行批改时,尽管扭转了以后的 URL,但浏览器不会立刻向后端发送申请。

Vue.extend 作用和原理

官网解释:Vue.extend 应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。

其实就是一个子类结构器 是 Vue 组件的外围 api 实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并

  • extend是结构一个组件的语法器。而后这个组件你能够作用到Vue.component这个全局注册办法里还能够在任意vue模板里应用组件。 也能够作用到vue实例或者某个组件中的components属性中并在外部应用apple组件。
  • Vue.component你能够创立 ,也能够取组件。

相干代码如下

export default function initExtend(Vue) {  let cid = 0; //组件的惟一标识  // 创立子类继承Vue父类 便于属性扩大  Vue.extend = function (extendOptions) {    // 创立子类的构造函数 并且调用初始化办法    const Sub = function VueComponent(options) {      this._init(options); //调用Vue初始化办法    };    Sub.cid = cid++;    Sub.prototype = Object.create(this.prototype); // 子类原型指向父类    Sub.prototype.constructor = Sub; //constructor指向本人    Sub.options = mergeOptions(this.options, extendOptions); //合并本人的options和父类的options    return Sub;  };}

v-once的应用场景有哪些

剖析

v-onceVue中内置指令,很有用的API,在优化方面常常会用到

体验

仅渲染元素和组件一次,并且跳过将来更新

<!-- single element --><span v-once>This will never change: {{msg}}</span><!-- the element have children --><div v-once>  <h1>comment</h1>  <p>{{msg}}</p></div><!-- component --><my-component v-once :comment="msg"></my-component><!-- `v-for` directive --><ul>  <li v-for="i in list" v-once>{{i}}</li></ul>

答复范例

  • v-oncevue的内置指令,作用是仅渲染指定组件或元素一次,并跳过将来对其更新
  • 如果咱们有一些元素或者组件在初始化渲染之后不再须要变动,这种状况下适宜应用v-once,这样哪怕这些数据变动,vue也会跳过更新,是一种代码优化伎俩
  • 咱们只须要作用的组件或元素上加上v-once即可
  • vue3.2之后,又减少了v-memo指令,能够有条件缓存局部模板并管制它们的更新,能够说控制力更强了
  • 编译器发现元素下面有v-once时,会将首次计算结果存入缓存对象,组件再次渲染时就会从缓存获取,从而防止再次计算

原理

上面例子应用了v-once

<script setup>import { ref } from 'vue'const msg = ref('Hello World!')</script><template>  <h1 v-once>{{ msg }}</h1>  <input v-model="msg"></template>

咱们发现v-once呈现后,编译器会缓存作用元素或组件,从而防止当前更新时从新计算这一部分:

// ...return (_ctx, _cache) => {  return (_openBlock(), _createElementBlock(_Fragment, null, [    // 从缓存获取vnode    _cache[0] || (      _setBlockTracking(-1),      _cache[0] = _createElementVNode("h1", null, [        _createTextVNode(_toDisplayString(msg.value), 1 /* TEXT */)      ]),      _setBlockTracking(1),      _cache[0]    ),// ...

Vue 组件间通信有哪几种形式?

Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。

(1)props / $emit 实用 父子组件通信 这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。

(2)ref 与 $parent / $children 实用 父子组件通信

  • ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例
  • $parent / $children:拜访父 / 子实例

(3)EventBus ($emit / $on) 实用于 父子、隔代、兄弟组件通信 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。

(4)$attrs/$listeners 实用于 隔代组件通信

  • $attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 ( class 和 style 除外 )。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 ( class 和 style 除外 ),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用。
  • $listeners:蕴含了父作用域中的 (不含 .native 润饰器的) v-on事件监听器。它能够通过 v-on="$listeners" 传入外部组件

(5)provide / inject 实用于 隔代组件通信 先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。 provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。 (6)Vuex 实用于 父子、隔代、兄弟组件通信 Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store” 基本上就是一个容器,它蕴含着你的利用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
  • 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。