前端面试之 Vue

207 阅读16分钟

Router 和 Route 的区别

Router

Router 是用于管理和处理应用程序中不同页面之间的导航的机制或者工具。通过在Router中可定义多个Route、并将其与特定的路径和相应的组件进行关联、进而构建出完整的路由系统。

用处

  1. 它可以监听URL的变化、并通过定义的规则来决定显示哪个组件。
  2. 它通常会提供一些方法或者函数来实现编程式导航、比如跳转到指定路径、替换当前路径等操作。
  3. Vue 的路由模式就是Router在处理路由时候的使用的模式。
  4. 导航守卫它也是Router提供的一种用于在路由导航的过程中对路由进行控制和管理的一种机制。而通过使用导航守卫机制、我们可以在路由跳转前后、以及在被跳转拒绝的时候执行特定的逻辑。

路由跳转

  1. 声明式导航

       <router-link :to="{path: '/linkTo', query: {name:'ming', age: 18 }}"> <button>跳转</button> </router-link>
       <router-link :to="{name: 'linkTo', params: {name:'ming', age: 18 }}"> <button>跳转</button> </router-link>
    
  2. 编程式导航

    • 参数会展示到URL上的、因此会有长度限制
          $Router.push({ 
              path: '/linkTo',
              query: { name: "ming", age: 18 }
          )
      
    • 参数不会展示到URL上的、因此不会有长度限制
          $Router.push({ 
              name: "abolinkTout", // ⚠️注:这里不能用path路径,只能用name
              params: { name: "ming", age: 18 }
          )
      
  3. 直接修改URL

    • 修改URL并添加新条目到历史记录中

      window.history.pushState(null, null, '/home');

    • 直接替换当前URL

      window.history.replaceState(null, null, '/about');

Route

描述了单个特定路径对应哪个组件从而进行渲染

包含什么

  • $route.path

    • 类型: string

      字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"

  • $route.params

    • 类型: Object

    一个 key/value 对象,包含了 动态片段 和 全匹配片段,如果没有路由参数,就是一个空对象。

  • $route.query

    • 类型: Object

      一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。

  • $route.hash

    • 类型: string

      当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。

  • $route.fullPath

    • 类型: string

      完成解析后的 URL,包含查询参数和 hash 的完整路径。

  • $route.name

    • 当前路由的名称,如果有的话。
  • $route.meta

    • 当前路由的元信息,如果有的话。

路由模式

Hash 模式(哈希模式)

Vue RouterHash模式下使用的是浏览器提供的hashchange事件来监听URL的变化。并根据新的Hash值找到对应的组件进行渲染。通过修改 URL 中的 hash 部分、就可以在无需向服务器发出请求的情况下进行页面之间的切换。

原理

  • 哈希模式是默认的 Vue 路由模式。
  • 在哈希模式下,URL 中会以 # 符号来表示路由路径,例如 http://example.com/#/home。
  • 哈希值变化不会触发页面刷新,所以可以实现无需向服务器请求资源而在前端进行路由导航。

History 模式(历史模式)

在历史模式下,可以使用 HTML5 History API 来实现前端路由导航。

HTML5 History API 提供了一组方法来操作浏览器的历史记录,包括添加、修改或删除条目。Vue Router 利用这些方法来改变 URL 地址,并根据新的路径找到对应的组件进行渲染。

  1. 手动更改 URL:你可以使用 pushState() 方法将新路径添加到浏览器历史记录中,并同时更新当前页面的 URL。
       state(可选):表示需要与新状态关联的对象。这个对象会在 popstate 事件中传递给处理函数。
       title(可选):表示新状态的标题,但现代浏览器大多忽略此参数。
       url(必需):表示新状态的 URL。
       
       history.pushState(state, title, url);
       
  1. 编程式导航:Vue Router 提供了编程式导航方法,以便在代码中进行路由跳转。

    • 使用 $router.push() 方法将新路径添加到浏览器历史记录中:

      this.$router.push('/home');
      
      
    • 使用 $router.replace() 方法替换当前浏览器历史记录中最后一个路径:

      this.$router.replace('/about');
      
      
  2. 删除条目:由于安全原因,无法直接删除特定历史记录条目。只能通过将用户导航到其他页面或刷新页面来间接删除它们。

  3. 监听 popstate 事件:当用户点击浏览器后退/前进按钮或通过其他方式修改了 URL 时,会触发 popstate 事件。你可以监听该事件并处理相应逻辑。例如:

    window.addEventListener('popstate', function(event) {
        console.log('URL changed to: ' + document.location.pathname);
        // 根据新的路径渲染对应组件
    });
    
    

请注意,在历史模式下,页面刷新不会丢失当前路径,并且可以使用浏览器的后退/前进按钮进行导航。但是,为了使路由在服务端正确响应,你需要配置服务器以始终返回 index.html(或其他入口文件),这样 Vue Router 才能捕获到 URL 变化并进行相应的渲染。

原理

  • 在历史模式下,URL 不再使用 hash 来表示路由路径,而是直接使用普通的 URL 形态。
  • 使用 HTML5 History API,在浏览器中修改 URL 并保持与服务器同步状态。这意味着你可以直接访问类似 /home 这样的路径,并且后端服务器也需要相应地进行配置支持。
  • 当用户手动输入或刷新页面时,在没有匹配到静态资源文件时可能出现 404 错误。解决这个问题需要服务器端的配置。

导航守卫

概念

在需要跳转的路由上用本地存储做字段标识, 然后与之前的字段做判断进行相应的跳转。

种类

全局守卫

  1. router.beforeEach 全局前置守卫
  2. router.beforeResolve 全局解析守卫
  3. router.afterEach 全局后置守卫

路由独享守卫

  1. 直接在路由配置上定义 beforeEnter 守卫

组件内的守卫

  1. beforeRouteEnter在渲染该组件的对应路由被验证前调用。
  2. beforeRouteUpdate在当前路由改变,但是该组件被复用时调用
  3. beforeRouteLeave 在导航离开渲染该组件的对应路由时调用。

导航解析全过程

  1. 导航被触发。
  2. 在失活的组件调用离开组件的守卫 beforeRouterLeave
  3. 在路由跳转之前调用全局前置守卫 beforeEach
  4. 在路由跳转且该组件被复用调用时调用 beforeRouterUpdate
  5. 在路由进入的时候调用路由独享守卫beforeEnter
  6. 解析异步路由组件。
  7. 在渲染该组件的对应路由被验证之前调用beforeRouterEnter
  8. 调用全局解析守卫beforeResolve
  9. 导航被确认。
  10. 调用全局后置守卫afterEach
  11. 触发DOM更新。
  12. 调用beforeRouterEnter并将创建好的组件实例当作参数传给next的回调函数。

自定义指令

在 Vue 中,自定义指令(Custom Directive)是一种用于扩展 Vue 的模板语法的机制。通过自定义指令,你可以在 DOM 元素上添加自定义行为,并在元素插入、更新和移除时进行相应的操作。

自定义指令由 Vue.directive 函数定义,它接收两个参数:指令名称和指令选项对象。指令选项对象包含一系列钩子函数,用于定义指令的行为。

使用

自定义指令是通过 Vue.directive() 方法来全局注册的,或者通过组件内部的 directives 选项来局部注册。

// 定义一个自定义指令
Vue.directive('focus', {
  // 绑定时
  bind(el, binding) {
    console.log('bind', el, binding);
  },
  
  // 插入时
  inserted(el) {
    el.focus();
  },
  
  // 更新时
  update(el, binding) {
    console.log('update', el, binding);
  },
  
  // 解绑时
  unbind(el) {
    console.log('unbind', el);
  }
});

以下是一些常见的自定义指令应用场景:

  1. 操作 DOM:自定义指令可以用于直接操作 DOM 元素,例如修改元素的样式、属性、事件绑定等。你可以通过在指令的钩子函数中访问和操作 DOM 元素。

  2. 表单验证:你可以创建自定义指令来实现表单验证逻辑。通过自定义指令,你可以监听输入框的值变化,并根据自定义的验证规则进行验证,以便提供实时的反馈。

  3. 权限控制:自定义指令可以用于权限控制场景,例如根据用户权限来隐藏或禁用某些元素。你可以在自定义指令中根据用户权限进行条件判断,并修改元素的显示或行为。

  4. 第三方库集成:当你需要在 Vue 中使用第三方库或插件时,可以使用自定义指令来进行集成。你可以创建一个自定义指令,在其中初始化和配置第三方库,并在适当的时机调用库的方法。

  5. 动画和过渡效果:自定义指令可以与 Vue 的过渡系统一起使用,实现自定义的动画和过渡效果。你可以在自定义指令中监听过渡钩子函数,并根据需要操作元素的样式或类名来实现过渡效果。

对于组件的封装有什么理解

在 Vue 中,组件的封装是将一部分功能、样式和模板等相关代码封装成一个独立的实体。这种封装能够提高代码的可复用性、可维护性和可测试性。

以下是我对 Vue 组件封装的理解:

  1. 功能独立:每个组件应该具备自己相对独立且完整的功能。例如,一个按钮组件应该包含点击事件处理、样式定义等内容,并且不依赖于其他组件或页面上下文。

  2. 参数接口(props):通过使用 props 属性,在父级组件中向子级传递数据和配置。这使得同一个或类似类型的组件可以在不同场景下被多次重用,并根据需要接收不同的属性值。

  3. 事件机制:Vue 的组件之间可以通过自定义事件进行通信。子级组件可以触发自定义事件并向父级发送消息,从而实现与外部环境之间的交互。

  4. 模板化:Vue 组件使用了基于 HTML 的模板语法来描述用户界面。通过编写模板,我们可以将数据动态渲染到视图中,并结合指令和条件渲染来控制显示逻辑。

  5. 样式隔离:Vue 组件支持添加作用域限定符(scoped),以确保每个组件的样式只在当前组件中生效,避免全局污染和样式冲突。

  6. 代码复用:通过封装可复用的组件,我们可以将常见的功能、布局或UI元素抽象为独立实体,并在不同页面或应用程序中多次使用。这样能够减少重复编写代码的工作量,并提高项目整体开发效率。

总结起来,Vue 组件封装是一种将相关功能、样式和模板(template)、数据(data)、方法(methods等内容封装成一个独立实体以提供可复用性和模块化的方式。它使得我们能够更好地管理项目代码,并且以更高效和灵活的方式构建用户界面。

响应式原理

响应式原理是指通过数据驱动视图。在Vue中,这主要通过数据劫持结合发布订阅模式实现。Vue会为组件的data选项中的每个属性添加gettersetter方法,并在其中实现依赖追踪和通知机制来实现的。也就是当数据发生变化时,会自动触发视图更新(也就是说这个过程是单向的)。

实现思路

  • Object.defineProperty生成的Observer针对对象或对象的属性进行递归"劫持"data选项中的每一项并添加gettersetter、以便在属性发生变化后通知订阅者

  • 解析器Compile解析模板中的Directive(指令),它负责扫描和解析元素节点的指令、然后会将模板中的变量替换成数据、等待数据变化然后进行渲染。同时在每个指令所对应的节点上绑定新的函数、添加订阅者、如果数据变化就更新视图

  • Watcher属于ObserverCompile桥梁,它将接收到的Observer产生的数据变化、同时调用自身的updata方法触发Compile的回调。

双向数据绑定

双向数据绑定则是一种更为复杂的机制,它不仅包括了数据变化驱动视图更新,还包括了视图中的变化(如用户输入)反过来影响数据本身,即视图和数据之间的双向动态关系。是在响应式的基础上,通过事件监听器将输入框的变化传递给Model层的数据,通常通过v-model指令实现

v-model 原理

定义

本质就是value+input方法的语法糖。可以通过model属性的prop+event属性来进行自定义原生的v-model,会根据标签的不同进而产生不同的事件。 默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event。

在自定义组件上使用 v-model

父组件给自定义组件上绑定v-model其实就是给子组件绑定了value属性,然后自定义组件内部用prop创建的value属性可以拿到父组件传递下来的值,名字必须是value。当自定义组件内部更改value时会通过$emit派发一个input事件并携带最新的值,然后v-model就会监听input事件,并将最新的值同步到v-model绑定的变量上面。

$on $emit

$on

是用来在监听(注册)自定义事件的。

  • 使用方法
        vm.$on(event, callback);
        
        // event {string | Array} (自定义事件的名称,可以使用数组的方式复数注册。数组方式必须在2.2.0+中才支持)
        // callback {Function} (自定义事件触发后,所执行的方法、函数)
    

$emit

是手动触发当前实例上的一个指定事件。

  • 使用方法
        vm.$emit(eventName, [...args]);
    
        //  eventName {string} (需要触发的事件名称)
        //  [...args] (传递的参数,多个参数用数组,单个参数就可以直接用参数本身的格式)
    

Vue 事件修饰符

为什么需要: 有时候我们只需要关注数据的逻辑即可、没必要关注 DOM 事件的细节。因为事件修饰符中已经帮我们处理了大量的 DOM 事件的细节

和指令之间有什么联系: 指令主要负责的是数据、事件的绑定,它是有独立的功能。而事件修饰符是对指令行为的补充或调整,必须与指令一起使用

事件修饰符

  1. .stop 阻止事件冒泡、相当于调用了event.stopPropagation方法
  2. .prevent 阻止了事件的默认行为,相当于调用了event.preventDefault方法。
  3. .self 只当在 event.target 是当前元素自身时触发处理函数。
  4. .once 绑定了事件以后只能触发一次,第二次就不会触发。
  5. .native 让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件。

表单修饰符

  1. .lazy 在我们填完信息,光标离开标签的时候,才会将值赋予给value
  2. .trim 自动过滤用户输入的首空格字符,而中间的空格不会过滤。
  3. .number 自动将用户的输入值转为数值类型,但如果这个值无法被 parseFloat 解析,则会返回原来的值。

v-bind修饰符

  1. async能对props进行一个双向绑定
  2. prop设置自定义标签属性,避免暴露数据,防止污染HTML结构
  3. camel将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox

键盘修饰符

如: .enter、 .tab、 .delete、 .esc 等等。

Vuex

概念

Vuex 是基于状态管理模式的库,它将应用程序的所有组件的共享状态存储在一个单一、可预测和可中心化的地方。

特点

1. 状态管理模式: Vuex 是基于状态管理模式的库,它将应用程序的所有组件的共享状态存储在一个单一、可预测和可中心化的地方。

2. 单向数据流: 在 Vuex 中,数据流是单向且明确定义的。所有对共享状态(即 store)进行修改只能通过提交 mutation 来完成,这样可以追踪每个变化,并记录下来。

3. State(状态): Vuex 使用 state 对象来存储应用程序级别的共享数据。State 可以被视为存放在全局变量中的响应式数据,在整个应用程序中都可以访问到。

4. Getter(获取器): Getter 允许从 store 中派生出一些新属性或计算值,并且这些派生值会自动跟踪依赖关系。Getter 可以看作是 store 的计算属性。

5. Mutation(突变): Mutation 是唯一允许修改 Vuex state 的方式。Mutation 必须是同步函数,并且可以带参数来接收负载(payload)对象传递额外信息。

6. Action(动作): Action 类似于 mutation, 但是它可以包含异步操作。Action 提交 mutation 来修改状态,而不是直接变更状态。

7. Module(模块): Vuex 允许将 store 分割成模块,每个模块拥有自己的 state、getter、mutation 和 action。这种方式使得大型应用程序的状态管理更加灵活和可维护。

8. 插件扩展: Vuex 提供了插件机制来扩展其功能。我们可以编写自定义插件来监听 mutation 或者添加额外的行为到Vuex中。

面试简记😘

image.png

当使用 Vuex 辅助函数时,可以更方便地在 Vue 组件中访问和操作 Vuex 的状态、派生值以及触发 mutation 和 action。以下是更详细的介绍:

1. mapState:

mapState 函数将 store 中的 state 映射到组件的计算属性中。它接受一个数组或者对象作为参数。

  • 传入数组时,数组元素是需要映射的 state 属性名。
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['count', 'isLoggedIn'])
  }
}

这样,在组件中就可以通过 this.countthis.isLoggedIn 来访问映射后的状态。

  • 传入对象时,对象键是自定义名称,值是需要映射的 state 属性名。
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState({
      totalCount: 'count',
      loggedInStatus: 'isLoggedIn'
    })
  }
}

然后,在组件内部就可以通过 this.totalCountthis.loggedInStatus 来获取对应映射后的状态。

2. mapGetters:

类似于 mapStatemapGetters 函数帮助我们将 getters 映射到组件的计算属性中。它也接受一个数组或者对象作为参数。

  • 使用数组语法来指定要映射的 getters 名称:
import { mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters(['doubleCount'])
  }
}

然后,在组件中就可以通过 this.doubleCount 来获取派生值。

  • 使用对象语法,键是自定义名称,值是要映射的 getters 名称:
import { mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters({
      totalCount: 'count',
      isLoggedInStatus: 'isLoggedIn'
    })
  }
}

现在,在组件内部就可以使用 this.totalCountthis.isLoggedInStatus 访问映射后的派生值。

3. mapMutations:

mapMutations 函数将 mutations 映射为当前组件方法,以便更方便地触发对应的 mutation 操作。它接受一个数组或者对象作为参数。

  • 使用数组来指定要映射的 mutation 名称:
import { mapMutations } from 'vuex';

export default {
  methods: {
    ...mapMutations(['increment', 'decrement'])
  }
}

这样,在组件中就可以直接调用 this.increment() 或者 this.decrement() 来触发相应的 mutation。

  • 使用对象语法,键是自定义名称,值是要映射的 mutation 名称:
import { mapMutations } from 'vuex';

export default {
  methods: {
    ...mapMutations({
      addValue: 'increment',
      subtractValue: 'decrement'
    })
  }
}

现在,在组件内部就可以通过 this.addValue()this.subtractValue() 来触发相应的 mutation。

4. mapActions:

mapActions 函数将 actions 映射为当前组件方法,使得我们可以更轻松地调用对应的 action。它也接受一个数组或者对象作为参数。

  • 使用数组来指定要映射的 action 名称:
import { mapActions } from 'vuex';

export default {
  methods: {
    ...mapActions(['incrementAsync', 'decrementAsync'])
  }
}

这样,在组件中就可以通过 this.incrementAsync() 或者 this.decrementAsync() 来触发相应的 action。

  • 使用对象语法,键是自定义名称,值是要映射的 action 名称:
import { mapActions } from 'vuex';

export default {
  methods: {
    ...mapActions({
      addValueAsync: 'incrementAsync',
      subtractValueAsync: 'decrementAsync'
    })
  }
}

现在,在组件内部就可以通过 this.addValueAsync()this.subtractValueAsync() 来触发相应的 action。