前端八股文自救指南——Vue——Day2

617 阅读5分钟

Vue

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

  • .stop:阻止事件冒泡。例如 <button @click.stop="handleClick">Click me</button>,点击按钮时,事件不会继续向上层元素传播。
  • .prevent:阻止默认事件。如 <form @submit.prevent="handleSubmit">...</form>,表单提交时不会触发默认的页面刷新行为。
  • .capture:使用事件捕获模式。事件在到达目标元素之前先触发绑定了该修饰符的元素的事件处理程序。
  • .self:只当事件是从绑定事件的元素本身触发时才触发事件处理程序。
  • .once:事件只触发一次。例如 <button @click.once="handleClick">Click me once</button>,按钮点击一次后,事件处理程序将不再响应后续点击。
  • .passive:用于滚动事件等,告诉浏览器事件处理程序不会调用 preventDefault(),可以提高滚动性能。

v-if 和 v-show 的区别

  • 原理不同

    • v-if 是真正的条件渲染,它会根据表达式的值动态地创建或销毁元素。当条件为 false 时,元素及其子元素会被完全从 DOM 中移除,再次变为 true 时会重新创建。
    • v-show 只是简单地通过修改元素的 display 属性来控制元素的显示与隐藏。无论条件是否为 true,元素始终会存在于 DOM 中。
  • 性能影响不同

    • v-if 有更高的切换开销,因为涉及到元素的创建和销毁。
    • v-show 有更高的初始渲染开销,因为元素始终存在于 DOM 中,但切换时性能较好。
  • 使用场景不同

    • v-if 适用于运行时条件很少改变的情况。
    • v-show 适用于需要频繁切换显示状态的场景。

v-html 的原理

v-html 是 Vue 提供的一个指令,用于将数据作为 HTML 插入到元素中。其原理是通过 innerHTML 属性将绑定的数据渲染到元素内部。例如:

<template>
  <div v-html="htmlContent"></div>
</template>
<script>
export default {
  data() {
    return {
      htmlContent: '<p>Hello, <strong>World!</strong></p>'
    };
  }
};
</script>

需要注意的是,使用 v-html 可能会存在 XSS(跨站脚本攻击)风险,因为它会直接将数据作为 HTML 解析并插入到页面中。如果数据来自用户输入或不可信的来源,应该对数据进行过滤和转义处理。

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

v-model 是 Vue 提供的一个语法糖,用于在表单元素(如 inputtextareaselect 等)上实现双向数据绑定。

  • 原理v-model 实际上是 :value 和 @input 事件的组合。对于不同的表单元素,v-model 的具体实现略有不同:

<template>
  <input v-model="message" />
  <!-- 等价于 -->
  <input :value="message" @input="message = $event.target.value" />
</template>
<script>
export default {
  data() {
    return {
      message: ''
    };
  }
};
</script>
  • 复选框(input[type="checkbox"]

<template>
  <input type="checkbox" v-model="isChecked" />
  <!-- 等价于 -->
  <input type="checkbox" :checked="isChecked" @change="isChecked = $event.target.checked" />
</template>
<script>
export default {
  data() {
    return {
      isChecked: false
    };
  }
};
</script>
  • 下拉框(select

<template>
  <select v-model="selectedOption">
    <option value="option1">Option 1</option>
    <option value="option2">Option 2</option>
  </select>
  <!-- 等价于 -->
  <select :value="selectedOption" @change="selectedOption = $event.target.value">
    <option value="option1">Option 1</option>
    <option value="option2">Option 2</option>
  </select>
</template>
<script>
export default {
  data() {
    return {
      selectedOption: 'option1'
    };
  }
};
</script>

data是函数而非对象

在 Vue 组件中,data 选项要求是一个函数而不是对象,主要是为了避免组件实例之间的数据共享问题。

  • 当 data 是一个对象时,所有组件实例都会共享同一个数据对象。这意味着一个组件实例对数据的修改会影响到其他组件实例,这通常不是我们想要的结果。

  • 当 data 是一个函数时,每个组件实例都会调用这个函数,返回一个新的数据对象。这样每个组件实例都有自己独立的数据副本,修改一个组件实例的数据不会影响到其他组件实例。

// 错误示例:data 为对象
export default {
  data: {
    count: 0
  }
};

// 正确示例:data 为函数
export default {
  data() {
    return {
      count: 0
    };
  }
};

mixin 和 mixins 区别

  • mixin:是一个对象,包含了可复用的组件选项,如 datamethodscomputed 等。可以将多个组件中共有的逻辑提取到一个 mixin 对象中,然后在需要的组件中使用。

// mixin.js
export const myMixin = {
  data() {
    return {
      message: 'Hello from mixin'
    };
  },
  methods: {
    showMessage() {
      console.log(this.message);
    }
  }
};
  • mixins:是组件选项,用于引入一个或多个 mixin 对象。在组件中使用 mixins 选项将 mixin 对象合并到组件中。

<template>
  <div>
    <button @click="showMessage">Show Message</button>
  </div>
</template>
<script>
import { myMixin } from './mixin.js';
export default {
  mixins: [myMixin]
};
</script>

路由的 hash 和 history 模式的区别

  • URL 表现形式

    • hash 模式:URL 中会带有 # 符号,例如 http://example.com/#/home# 后面的内容称为 hash 值,hash 值的变化不会向服务器发送请求。
    • history 模式:URL 看起来更像传统的 URL,没有 # 符号,例如 http://example.com/home
  • 原理

    • hash 模式:通过监听 hashchange 事件来实现路由切换,当 hash 值发生变化时,Vue Router 会根据新的 hash 值渲染对应的组件。
    • history 模式:使用 HTML5 的 History API 来实现路由切换,主要使用 pushState 和 replaceState 方法来改变浏览器的历史记录,从而实现无刷新的页面切换。
  • 服务器配置

    • hash 模式:由于 hash 值的变化不会向服务器发送请求,所以不需要特殊的服务器配置。
    • history 模式:因为没有 # 符号,当用户直接访问某个 URL 时,服务器需要进行相应的配置,将所有请求都指向同一个 HTML 文件,由前端路由来处理具体的路由逻辑。否则,服务器可能会返回 404 错误。

router 和 route 的区别

  • router:是 Vue Router 的实例,是一个全局的对象,用于管理整个应用的路由。可以通过 this.$router 在组件中访问。router 提供了很多方法,如 pushreplacego 等,用于实现路由导航。

// 跳转到指定路由
this.$router.push('/home');
  • route:是当前激活的路由信息对象,包含了当前路由的路径、参数、查询字符串等信息。可以通过 this.$route 在组件中访问。

// 获取当前路由的参数
const id = this.$route.params.id;

如何设置动态路由

在 Vue Router 中,可以通过在路由配置中使用动态路径参数来设置动态路由。动态路径参数以冒号 : 开头,后面跟着参数名。

import Vue from 'vue';
import VueRouter from 'vue-router';
import User from './components/User.vue';

Vue.use(VueRouter);

const routes = [
  {
    path: '/user/:id',
    component: User
  }
];

const router = new VueRouter({
  routes
});

export default router;

在上述示例中,/user/:id 表示这是一个动态路由,:id 是动态路径参数。在 User 组件中,可以通过 this.$route.params.id 来获取当前路由的 id 参数值。

路由守卫

路由守卫是 Vue Router 提供的一种机制,用于在路由切换前后执行一些逻辑,如验证用户登录状态、权限检查等。常见的路由守卫有以下几种:

  • 全局前置守卫:在每次路由切换前都会执行。

router.beforeEach((to, from, next) => {
  // to: 即将要进入的目标路由对象
  // from: 当前导航正要离开的路由对象
  // next: 必须调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login');
  } else {
    next();
  }
});
  • 路由独享守卫:在某个路由配置中定义,只对该路由生效。

const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      if (isAdmin()) {
        next();
      } else {
        next('/');
      }
    }
  }
];
  • 组件内守卫:在组件内部定义,用于控制组件的路由行为。

<template>
  <div>...</div>
</template>
<script>
export default {
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不能获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
    next();
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 可以访问组件实例 `this`
    next();
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
    next();
  }
};
</script>

Vue 中 key 的作用,为什么不建议用 index 作为 key?

  • key 的作用key 是 Vue 中用于识别节点的唯一标识。在进行列表渲染时,Vue 会根据 key 来判断哪些元素发生了变化,从而尽可能复用已有的 DOM 元素,提高渲染性能。

  • 不建议用 index 作为 key 的原因

    • 数据顺序变化时会导致错误更新:当列表数据的顺序发生变化时,使用 index 作为 key 会使 Vue 错误地复用 DOM 元素,导致一些意外的问题,如输入框内容丢失、动画异常等。

    • 数据删除或插入时会影响性能:当列表中插入或删除元素时,使用 index 作为 key 会使 Vue 重新渲染大量的 DOM 元素,降低性能。

建议使用列表中具有唯一标识的数据属性作为 key,如 id

<template>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.name }}</li>
  </ul>
</template>
<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' }
      ]
    };
  }
};
</script>

为什么 v-for 和 v-if 不能一起使用

v-for 的优先级比 v-if 高,当它们一起使用时,v-if 会在每次 v-for 循环时都执行一次。这会导致不必要的性能开销,因为即使某些元素不需要渲染,v-if 也会对每个元素进行判断,例如:

<template>
  <ul>
    <li v-for="item in items" v-if="item.isVisible">{{ item.name }}</li>
  </ul>
</template>

上述代码中,v-if 会在每次 v-for 循环时都对 item.isVisible 进行判断。

更好的做法是将 v-if 移到 v-for 的外层,或者使用计算属性过滤数据。

<template>
  <ul>
    <li v-for="item in visibleItems" :key="item.id">{{ item.name }}</li>
  </ul>
</template>
<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: 'Item 1', isVisible: true },
        { id: 2, name: 'Item 2', isVisible: false },
        { id: 3, name: 'Item 3', isVisible: true }
      ]
    };
  },
  computed: {
    visibleItems() {
      return this.items.filter(item => item.isVisible);
    }
  }
};
</script>

这样可以避免不必要的性能开销,提高渲染效率。