25年整理高频【前端面试题】(Vue篇)

1.Vue的基本原理

  1. 当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使用proxy )将它们转为 getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。
  2. 每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

2.双向数据绑定的原理

  1. 数据劫持(Data Hijacking)

    Vue 使用 Object.defineProperty() 方法对数据对象的属性进行拦截和劫持。这样,当数据发生变化时,可以通知依赖这些数据的视图进行更新。

    const data = { message: 'Hello, Vue!' };
    
    Object.keys(data).forEach(key => {
      let value = data[key];
      Object.defineProperty(data, key, {
        get() {
          console.log(`获取属性 ${key}`);
          return value;
        },
        set(newValue) {
          console.log(`设置属性 ${key} 为 ${newValue}`);
          value = newValue;
          // 通知依赖更新
        }
      });
    });
    
  2. 发布-订阅模式(Publish-Subscribe Pattern)

    在Vue中,每个响应式属性都有一个依赖(Dep)对象,存储所有依赖于该属性的订阅者(即观察者,Watcher)。当属性变化时,Dep会通知所有订阅者进行更新。

    依赖收集(Dependency Collection)

    当Vue实例化时,Watcher会读取数据属性,并将自己添加到该属性的依赖列表中。

    class Dep {
      constructor() {
        this.subscribers = [];
      }
    
      addSub(sub) {
        this.subscribers.push(sub);
      }
    
      notify() {
        this.subscribers.forEach(sub => {
          sub.update();
        });
      }
    }
    
    class Watcher {
      constructor(obj, key, callback) {
        this.obj = obj;
        this.key = key;
        this.callback = callback;
        this.value = this.get();
      }
    
      get() {
        Dep.target = this;
        const value = this.obj[this.key];
        Dep.target = null;
        return value;
      }
    
      update() {
        const value = this.obj[this.key];
        this.callback(value);
      }
    }
    
    const data = { message: 'Hello, Vue!' };
    const dep = new Dep();
    
    Object.keys(data).forEach(key => {
      let value = data[key];
      Object.defineProperty(data, key, {
        get() {
          if (Dep.target) {
            dep.addSub(Dep.target);
          }
          return value;
        },
        set(newValue) {
          value = newValue;
          dep.notify();
        }
      });
    });
    
    // 使用观察者
    new Watcher(data, 'message', (newVal) => {
      console.log(`视图更新: ${newVal}`);
    });
    
    data.message = 'Hello, World!';  // 输出: 视图更新: Hello, World!
    
  3. 模版编译和更新

    Vue 的模版编译器会将模版编译成渲染函数,渲染函数会读取响应式数据并生成虚拟DOM。当数据变化时,Watcher会触发视图更新,新的虚拟DOM会和旧的虚拟DOM进行比较(diff算法),找到最小的差异,并只更新那些实际变化的部分。

  4. 双向数据绑定

    • 绑定输入事件:监听表单元素的 input 事件,当用户输入时,更新数据模型。
    • 绑定视图更新:当数据模型变化时,更新表单元素的值。

总结:首先数据劫持,用Object.defineProperty()给每个属性加上getter和setter。有一个watcher类,watcher在实例化的时候就会获取一次数据的值从而触getter,getter会把watcher添加进依赖Dep。Dep是用来管理watcher的,他有把watcher添加进管理列表subscribers的方法addSub()和通知方法notify()。当数据更新的时候,触发响应式数据的setter,就会调用Dep中的notify(),会通知subscribers中所有的watcher调用他们的update()方法进行更新。

3.使用 Object.defineProperty() 来进行数据劫持有什么缺点?

在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。

在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。

4.computed和watch

  • computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
  • watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。

5.slot是什么?有什么作用?原理是什么?

slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。

  • 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
  • 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。

实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽

6.如何保存页面的当前的状态

  • 组件会被卸载

    1. 将状态存储在LocalStorage / SessionStorage
    2. 各种传值
  • 组件不会被卸载

    直接把页面写成一个组件,控制他隐藏。

  • keep-alive

    <keep-alive>包裹,这个时候组件就不会执行一系列钩子函数,只会执行activateddeactivated

    //组件中
    <keep-alive>
    	<router-view v-if="$route.meta.keepAlive"></router-view>
    </kepp-alive>
    
    //router.js
    {
      path: '/',
      name: 'xxx',
      component: ()=>import('../src/views/xxx.vue'),
      meta:{
        keepAlive: true // 需要被缓存
      }
    },
    

7.常见的事件修饰符及其作用

  • .stop:等同于 JavaScript 中的 event.stopPropagation() ,防止事件冒泡;
  • .prevent :等同于 JavaScript 中的 event.preventDefault() ,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
  • .capture :与事件冒泡的方向相反,事件捕获由外到内;
  • .self :只会触发自己范围内的事件,不包含子元素;
  • .once :只会触发一次。

8.v-model 是如何实现的,语法糖实际是什么?

就是语法糖,省略了一些代码,实际上就是绑定了一个事件,传递了一个prop。

  • 用在表单上

    <input v-model="sth" />
    //  等同于
    <input 
        v-bind:value="message" 
        v-on:input="message=$event.target.value"
    >
    //$event 指代当前触发的事件对象;
    //$event.target 指代当前触发的事件对象的dom;
    //$event.target.value 就是当前dom的value值;
    //在@input方法中,value => sth;
    //在:value中,sth => value;
    
  • 用在自定义组件上

    // 父组件
    <aa-input v-model="aa"></aa-input>
    // 等价于
    <aa-input v-bind:value="aa" v-on:input="aa=$event.target.value"></aa-input>
    

    在自定义组件中Vue2和Vue3有些许的不同,Vue2中v-model在同一个组件上只能用一次,想要用多次就用.sync

    <!-- Test.vue -->
    <CustomInput :firstName.sync="obj.firstName" :lastName.sync="obj.lastName"></CustomInput>
    
    <!-- CustomInput.vue -->
    <template>
      <div>
        <div><span>{{firstName}}</span><span @click="changeX">更改姓</span></div>
        <div><span>{{lastName}}</span><span @click="changeM">更改名</span></div>
      </div>
     </template>
    <script>
    export default {
      props: {
        firstName: String,
        lastName: String
      },
      methods: {
        changeX () {
          this.$emit('update:firstName', 'liu')
        },
        changeM () {
          this.$emit('update:lastName', 'yz')
        }
      }
    }
    </script>
    

    Vue3中可以用多次v-model,但是要定义一下名称

      <!-- Test.vue -->
      <MyComponent
        v-model:first-name="first"
        v-model:last-name="last"
      />
      
      <!-- MyComponent.vue -->
      defineProps({
        firstName: String,
        lastName: String
      })
      
      defineEmits(['update:firstName', 'update:lastName'])
      </script>
      
      <template>
        <input
          type="text"
          :value="firstName"
          @input="$emit('update:firstName', $event.target.value)"
        />
        <input
          type="text"
          :value="lastName"
          @input="$emit('update:lastName', $event.target.value)"
        />
      </template>
    

9.data为什么是一个函数而不是对象

Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态变更将会影响所有组件实例。

10.对keep-alive的理解,它是如何实现的,具体缓存的是什么?

把组件的实例添加到缓存里面,并且缓存它的key。当组件切换回来的时候会在cache对象中找到对应的实例,重新激活。

11.$nextTick 原理及作用

在 Vue 中,数据的变化会触发 DOM 更新,但这种更新是异步执行的,以便在同一个事件循环中多次数据修改只会触发一次 DOM 更新。$nextTick 方法允许你在 DOM 更新完成后执行代码,这对需要依赖最新 DOM 状态的操作非常有用。

12.Vue 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决?

视图不会有变化,除非用了api $set()

<template> 
   <div>
      <ul>
         <li v-for="value in obj" :key="value"> {{value}} </li> 
      </ul> 
      <button @click="addObjB">添加 obj.b</button> 
   </div>
</template>

<script>
    export default { 
       data () { 
          return { 
              obj: { 
                  a: 'obj.a' 
              } 
          } 
       },
       methods: { 
          addObjB () { 
             this.$set(this.obj, 'b', 'obj.b')
          } 
      }
   }
</script>

13.Vue中封装的数组方法有哪些,其如何实现页面更新

push(),pop(),shift(),unshift(),splice(),sort(),reverse()

  1. 创建 arrayMethods 对象:首先,创建一个对象 arrayMethods,它继承自 Array.prototype,并对数组的变更方法进行重写。
  2. 重写数组方法:对每个需要重写的方法(如 pushpop 等),使用 Object.defineProperty 定义新的方法。在新的方法中,首先调用原始的数组方法,然后进行依赖通知。
  3. 依赖通知:重写的方法在执行原始操作后,会调用观察者的 dep.notify() 方法,通知所有依赖该数组的观察者,从而触发视图更新。
  4. 观察新插入的元素:对于 pushunshiftsplice 方法,会对新插入的元素进行观察,以确保这些新元素也是响应式的。

14.Vue template 到 render 的过程

  1. 模板解析和编译

    • 模板解析(Parsing) :将模板字符串解析成抽象语法树(AST)。
    • 优化(Optimization) :标记静态节点和静态根节点,静态节点生成的DOM不会变化,以提升性能。
    • 代码生成(Code Generation) :将优化后的 AST 转换成渲染函数的代码字符串
  2. 渲染函数执行和虚拟 DOM 生成

    • 渲染函数在组件实例的上下文中执行,返回一个虚拟 DOM 树(VNode Tree)。
  3. 虚拟 DOM 渲染成真实 DOM

    • 初始渲染:将虚拟 DOM 树转换为真实 DOM 元素,并插入到页面中。
    • 更新渲染:在数据更新时,生成新的虚拟 DOM 树,与旧的虚拟 DOM 树进行比较(diff 算法),找出需要更新的部分,并进行最小量的 DOM 操作以更新视图。

15.Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

不会的,DOM的更新是异步的,会把所有的DOM更新事件放在一个队列里面,一起更新。

16.简述 mixin、extends 的覆盖逻辑

  1. 生命周期钩子:生命周期钩子(如 createdmounted 等)会合并成一个数组,所有的钩子函数会按顺序执行。先执行 extends 中的钩子函数,再执行 mixin 中的钩子函数,最后执行组件自身的钩子函数。
  2. 数据(data) :数据选项会进行递归合并。如果存在同名的属性,组件自身的数据属性会覆盖 extendsmixin 中的数据属性。注意,数据属性在合并时是浅合并。
  3. 方法(methods) :方法选项会合并成一个对象。如果存在同名的方法,组件自身的方法会覆盖 extendsmixin 中的方法。
  4. 计算属性(computed) :计算属性的合并逻辑与方法类似,同名的计算属性会被覆盖。
  5. 其他选项:其他选项(如 componentsdirectives 等)会进行递归合并,组件自身的选项会优先覆盖 extendsmixin 中的选项。

总结就是,对于生命周期不同的钩子会按顺序执行,同一个钩子会先extends,再mixin,最后才是组件。对于其他的,同名的情况下组件自身的会覆盖mixin和extends的。

17.自定义指令

例如实现一个按钮防抖

import Vue from 'vue';

Vue.directive('debounce', {
  bind(el, binding) {
    if (typeof binding.value !== 'function') {
      console.warn(`Expect a function, got ${typeof binding.value}`);
      return;
    }
    // 使用防抖函数包装传入的函数
    const debouncedFn = debounce(binding.value, binding.arg || 300);
    // 将防抖函数存储在元素的自定义属性中,以便在 unbind 钩子中可以访问
    el.__debouncedFn__ = debouncedFn;
    // 添加事件监听器
    el.addEventListener('click', debouncedFn);
  },
  unbind(el) {
    // 移除事件监听器
    el.removeEventListener('click', el.__debouncedFn__);
    // 删除自定义属性
    delete el.__debouncedFn__;
  }
});
  • 全局注册

    //写在main.js中
    
    Vue.directive('focus', {
      // 当绑定元素插入到 DOM 中时
      inserted(el) {
        el.focus();
      }
    });
    
  • 局部注册

    //写在组件内
    
    const MyComponent = {
      directives: {
        focus: {
          inserted(el) {
            el.focus();
          }
        }
      }
    };
    

自定义指令的生命周期:

  1. bind: 只调用一次,指令第一次绑定到元素时调用。这是进行一次性初始化的好地方。
  2. inserted: 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
  3. update: 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
  4. componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  5. unbind: 只调用一次,指令与元素解绑时调用。

18.assets和static的区别

assets中的东西会打包,而static不会,所以已经第三方的资源文件可以放在static中,因为这些东西已经压缩处理过了不需要再处理一次。

19.Vue的性能优化有哪些

  • 编码阶段

    • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
    • v-if和v-for不能连用
    • 如果需要使用v-for给每项元素绑定事件时使用事件代理
    • SPA 页面采用keep-alive缓存组件
    • 在更多的情况下,使用v-if替代v-show
    • key保证唯一
    • 使用路由懒加载、异步组件
    • 防抖、节流
    • 第三方模块按需导入
    • 长列表滚动到可视区域动态加载
    • 图片懒加载
  • SEO优化

    • 预渲染
    • 服务端渲染
  • 打包优化

    • 压缩代码
    • Tree Shaking/Scope Hoisting
    • 使用cdn加载第三方模块
    • 多线程打包happypack
    • splitChunks抽离公共文件
    • sourceMap优化
  • 用户体验

    • 骨架屏
    • 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

20.v-if和v-for哪个优先级更高?如果同时出现,应如何优化?

Vue2,Vue3两边不一样,但是总而言之不要一起用。如果需要先做判断再v-for,那就可以使用computed计算过后再v-for。

21.说一下Vue的生命周期

  1. beforeCreate(创建前) :数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
  2. created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。
  3. beforeMount(挂载前) :在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
  4. mounted(挂载后) :在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。
  5. beforeUpdate(更新前) :响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
  6. updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  7. beforeDestroy(销毁前) :实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。
  8. destroyed(销毁后) :实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
  9. 特殊:actived和deactived,是使用了<keeep-alive>包裹的组件独有的,分别对应消失和隐藏。

22.Vue 子组件和父组件执行顺序

加载渲染过程:

  1. 父组件 beforeCreate
  2. 父组件 created
  3. 父组件 beforeMount
  4. 子组件 beforeCreate
  5. 子组件 created
  6. 子组件 beforeMount
  7. 子组件 mounted
  8. 父组件 mounted

更新过程:

  1. 父组件 beforeUpdate
  2. 子组件 beforeUpdate
  3. 子组件 updated
  4. 父组件 updated

销毁过程:

  1. 父组件 beforeDestroy
  2. 子组件 beforeDestroy
  3. 子组件 destroyed
  4. 父组件 destoryed

23.组件通信

  • 父传子

    在父组件中用v-bind传递数据,子组件用props接受。

  • 字传父

    父组件中写一个方法用v-on绑定一个事件在子组件上,子组件用$emit()触发事件,数据以函数入参的方式传给父组件。

  • 兄弟组件

    用EventBus全局事件总线传值。

  • 祖先传后代

    1. 用provide / inject

      大致就是在祖先中写provide提供数据,后代用inject接受数据,但是不好用我感觉,还不如用eventBus。

    2. pinia / vuex

  • 其他冷门的方法

    1. ref / $ref

      在子组件上标记ref,然后获取子组件实例,使用子组件的方法。

    2. parent/parent / parent/children

      已经不推荐用了,别用。

24.Vue-Router 的懒加载如何实现

正常写法

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技术

    // r就是resolve
    const List = r => require.ensure([], () => r(require('@/components/list')), 'list');
    // 路由也是正常的写法  这种是官方推荐的写的 按模块划分懒加载 
    const router = new Router({
      routes: [
      {
        path: '/list',
        component: List,
        name: 'list'
      }
     ]
    }))
    

25.hash路由和history路由

一个是修改#后面的hash值进行路由跳转,一个是修改.com/后面的路径,history路由要后台支持,比如修改NGINX的配置,禁止服务器自动匹配资源,将请求交给Vue来处理。

location / {
    root   html;
    try_files $uri /index.html;
}

26.如何定义动态路由?如何获取传过来的动态参数?

  1. param方式

    • 配置路由格式:/router/:id
    • 传递的方式:在path后面跟上对应的值
    • 传递后形成的路径:/router/123
    • 通过 $route.params.id 获取传递的值
  2. query方式

    • 配置路由格式:/router,也就是普通配置
    • 传递的方式:对象中使用query的key作为传递方式
    • 传递后形成的路径:/route?id=123
    • 通过$route.query 获取传递的值

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

  • 全局钩子

    • router.beforeEach 全局前置守卫 进入路由之前
    • beforeResolve 全局解析守卫
    • router.afterEach 全局后置钩子 进入路由之后
    router.beforeEach((to, from) => {  
       ...
    });
    
    router.afterEach((to, from) => {  
       ...
    });
    
  • 组件内钩子

    • beforeRouteUpdate

      组件被复用但是地址有改变,比如说传参变了。

    • beforeRouteEnter

      beforeRouteEnter(to, from, next) {      
          next(target => {        
              if (from.path == '/classProcess') {          
                  target.isFromProcess = true        
              }      
          })    
      }
      
    • beforeRouteLeave 同上

  • 单个路由钩子

    在路由表里面写的

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

假如说从A页面到B页面完整的顺序

beforeRouteLeave beforeEach beforeEnter beforeRouteEnter beforeResolve afterEach 然后组件生命周期

28.params和query的区别

有两种方式写路由跳转,一种叫编程式导航,还有种叫声明式导航。

第一种params会以路径的形式在url中显示参数,第二种使用编程式导航写params,但是这种方式刷新会丢失参数,导致页面显示有问题。query传参参数会以查询参数的形式在url中显示参数。

29.Vuex

Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 提交修改信息, Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action ,但 Action 也是无法直接修改 State 的,还是需要通过Mutation 来修改State的数据。最后,根据 State 的变化,渲染到视图上。

  1. State: Vuex 的状态存储是响应式的,当 State 中的数据变化时,依赖这些数据的组件会自动更新。
  2. Getters: 用于派生 State,类似于计算属性。
  3. Mutations: 是同步事务,用于更改 State。通过提交 Mutation,可以确保 State 的变化是可追踪的。
  4. Actions: 可以包含异步操作,通过分发 Action 来提交 Mutation,使得 State 的变化是可预测的。

30.Vue2和Vu3的区别

Vue2使用Object.defineProperty,Vue3使用proxy,Vue3有setup模式,Vue2是选项是api(options api),Vue3是组合式api(composition api)。巴拉巴拉的。Vue2太过依赖于this。

31. DIFF算法的原理

主要流程

  1. 初步对比

    • 从根节点开始,递归地比较新旧虚拟 DOM 树的每个节点。
    • 如果节点类型不同,直接替换整个节点。
    • 如果节点类型相同,则继续比较节点的属性和子节点。
  2. 更新属性

    • 对比新旧节点的属性,找出不同并进行更新。
    • 新增或删除属性。
    • 更新已有属性的值。
  3. 处理子节点

    • 使用 Keyed Diff 算法,通过节点的 key 属性唯一标识每个节点,从而提高比较效率。

      主要是建立了一个旧子节点的key-index映射表,方便快速判断子节点是否存在,定位子节点。(oldKeyToIndex列表,输入新子节点的key就可判断了。)

    • 双端比较用于进一步优化子节点的更新,减少节点的移动次数。

    • 具体来说,双端比较通过从头和尾同时进行对比,尽可能减少需要移动的节点数量。

      假如说就叫旧子节点list,新子节点list,还有对应的头尾指针;后面的判断如果是true就调用更新函数;先比较旧头部节点和新头部节点的key,再比较旧尾节点和新尾节点的key,再旧头和新尾,再旧尾和新头;还没有就直接查找,找不到就立即插入新指针的位置,找到了就根据key获取旧元素的index,插入当前的位置。

32.通过路由传递props(少见)

通过配置路由时设定props

const routes = [
  {
    path: '...',
    name: '...',
    component: ...,
    props: true //就是这里
  }
];

有三种模式:

  • 布尔值true

    会将params作为props传给组件,组件可以直接接受这个props,props的key就是占位符,比如/destination/:user,那组件接受props就是user。

  • 对象

    只能写死,把这个对象传给组件

    const routes = [
      {
        path: '/destination',
        name: 'destination',
        component: DestinationComponent,
        props: { user: 'JohnDoe' }
      }
    ];
    
    //组件接受的props就是user
    
  • 函数

    这种方法比较灵活,能同时传query形式的参数也能传递params,也最推荐。

    const routes = [
      {
        path: '/destination/:user',
        name: 'destination',
        component: DestinationComponent,
        props: route => ({ user: route.params.user, isAdmin: route.query.admin === 'true' })
      }
    ];
    

总结

专注收集整理Java面试题,不定期给大伙分享面试中的高频题与大厂难题。
如果你觉得文章对你有用,可以点赞和关注哦!❤
欢迎大家在评论区留下你宝贵的建议📢

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值