写点什么

一文梳理 vue 面试题知识点

作者:bb_xiaxia1998
  • 2022-10-24
    浙江
  • 本文字数:10176 字

    阅读完需:约 33 分钟

Vue3.0 和 2.0 的响应式原理区别

Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。


相关代码如下


import { mutableHandlers } from "./baseHandlers"; // 代理相关逻辑import { isObject } from "./util"; // 工具方法
export function reactive(target) { // 根据不同参数创建不同响应式对象 return createReactiveObject(target, mutableHandlers);}function createReactiveObject(target, baseHandler) { if (!isObject(target)) { return target; } const observed = new Proxy(target, baseHandler); return observed;}
const get = createGetter();const set = createSetter();
function createGetter() { return function get(target, key, receiver) { // 对获取的值进行放射 const res = Reflect.get(target, key, receiver); console.log("属性获取", key); if (isObject(res)) { // 如果获取的值是对象类型,则返回当前对象的代理对象 return reactive(res); } return res; };}function createSetter() { return function set(target, key, value, receiver) { const oldValue = target[key]; const hadKey = hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); if (!hadKey) { console.log("属性新增", key, value); } else if (hasChanged(value, oldValue)) { console.log("属性值被修改", key, value); } return result; };}export const mutableHandlers = { get, // 当获取属性时调用此方法 set, // 当修改属性时调用此方法};
复制代码

mixin 和 mixins 区别

mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。


Vue.mixin({  beforeCreate() {    // ...逻辑        // 这种方式会影响到每个组件的 beforeCreate 钩子函数  },});
复制代码


虽然文档不建议在应用中直接使用 mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。


mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。

delete 和 Vue.delete 删除数组的区别

  • delete 只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。

  • Vue.delete 直接删除了数组 改变了数组的键值。

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

Vue 模版编译原理知道吗,能简单说一下吗?

简单说,Vue 的编译过程就是将template转化为render函数的过程。会经历以下阶段:


  • 生成 AST 树

  • 优化

  • codegen


首先解析模版,生成AST语法树(一种用 JavaScript 对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。


Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的 DOM 也不会变化。那么优化过程就是深度遍历 AST 树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。


编译的最后一步是将优化后的AST树转换为可执行的代码

Vue 组件 data 为什么必须是个函数?

  • 根实例对象data可以是对象也可以是函数 (根实例是单例),不会产生数据污染情况

  • 组件实例对象data必须为函数 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数,


简版理解


// 1.组件的渲染流程 调用Vue.component -> Vue.extend -> 子类 -> new 子类// Vue.extend 根据用户定义产生一个新的类function Vue() {}function Sub() { // 会将data存起来    this.data = this.constructor.options.data();}Vue.extend = function(options) {    Sub.options = options; // 静态属性    return Sub;}let Child = Vue.extend({    data:()=>( { name: 'zf' })});
// 两个组件就是两个实例, 希望数据互不感染let child1 = new Child();let child2 = new Child();
console.log(child1.data.name);child1.data.name = 'poetry';console.log(child2.data.name);
// 根不需要 任何的合并操作 根才有vm属性 所以他可以是函数和对象 但是组件mixin他们都没有vm 所以我就可以判断 当前data是不是个函数
复制代码


相关源码


// 源码位置 src/core/global-api/extend.jsexport function initExtend (Vue: GlobalAPI) {  Vue.extend = function (extendOptions: Object): Function {    extendOptions = extendOptions || {}    const Super = this    const SuperId = Super.cid    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})    if (cachedCtors[SuperId]) {      return cachedCtors[SuperId]    }
const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name) }
const Sub = function VueComponent (options) { this._init(options) } // 子类继承大Vue父类的原型 Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++ Sub.options = mergeOptions( Super.options, extendOptions ) Sub['super'] = Super
// For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) }
// allow further extension/mixin/plugin usage Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use
// create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub // 记录自己 在组件中递归自己 -> jsx }
// keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options)
// cache constructor cachedCtors[SuperId] = Sub return Sub }}
复制代码

Vue 模版编译原理

vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。


  • 解析阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树 AST。

  • 优化阶段:遍历 AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行 diff 比较时,直接跳过这一些静态节点,优化 runtime 的性能。

  • 生成阶段:将最终的 AST 转化为 render 函数字符串。

什么是 mixin ?

  • Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。

  • 如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。

  • 然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

双向数据绑定的原理

Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:


  1. 需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

  2. compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

  3. Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个 update()方法 ③待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。

  4. MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。


参考:前端vue面试题详细解答

Vue 中的 key 到底有什么用?

key是为 Vue 中的 vnode 标记的唯一 id,通过这个 key,我们的 diff 操作可以更准确、更快速


diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.


diff 程可以概括为:oldCh 和 newCh 各有两个头尾的变量 StartIdx 和 EndIdx,它们的 2 个变量相互比较,一共有 4 种比较方式。如果 4 种比较都没匹配,如果设置了 key,就会用 key 进行比较,在比较的过程中,变量会往中间靠,一旦 StartIdx>EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.


  • 准确: 如果不加key,那么 vue 会选择复用节点(Vue 的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的 bug.

  • 快速: key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1).

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

在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。


在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用 Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。

vue-router 路由钩子函数是什么 执行顺序是什么

路由钩子的执行流程, 钩子函数种类有:全局守卫、路由守卫、组件守卫


完整的导航解析流程:


  1. 导航被触发。

  2. 在失活的组件里调用 beforeRouteLeave 守卫。

  3. 调用全局的 beforeEach 守卫。

  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。

  5. 在路由配置里调用 beforeEnter。

  6. 解析异步路由组件。

  7. 在被激活的组件里调用 beforeRouteEnter。

  8. 调用全局的 beforeResolve 守卫 (2.5+)。

  9. 导航被确认。

  10. 调用全局的 afterEach 钩子。

  11. 触发 DOM 更新。

  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

用过 pinia 吗?有什么优点?

1. pinia 是什么?


  • Vue3中,可以使用传统的Vuex来实现状态管理,也可以使用最新的pinia来实现状态管理,我们来看看官网如何解释pinia的:PiniaVue 的存储库,它允许您跨组件/页面共享状态。

  • 实际上,pinia就是Vuex的升级版,官网也说过,为了尊重原作者,所以取名pinia,而没有取名Vuex,所以大家可以直接将pinia比作为Vue3Vuex


2. 为什么要使用 pinia?


  • Vue2Vue3都支持,这让我们同时使用Vue2Vue3的小伙伴都能很快上手。

  • pinia中只有stategetteraction,抛弃了Vuex中的MutationVuexmutation一直都不太受小伙伴们的待见,pinia直接抛弃它了,这无疑减少了我们工作量。

  • piniaaction支持同步和异步,Vuex不支持

  • 良好的Typescript支持,毕竟我们Vue3都推荐使用TS来编写,这个时候使用pinia就非常合适了

  • 无需再创建各个模块嵌套了,Vuex中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响。

  • 体积非常小,只有1KB左右。

  • pinia支持插件来扩展自身功能。

  • 支持服务端渲染


3. pinna 使用


pinna文档(opens new window)


  1. 准备工作


我们这里搭建一个最新的Vue3 + TS + Vite项目


npm create vite@latest my-vite-app --template vue-ts
复制代码


  1. pinia基础使用


yarn add pinia
复制代码


// main.tsimport { createApp } from "vue";import App from "./App.vue";import { createPinia } from "pinia";const pinia = createPinia();
const app = createApp(App);app.use(pinia);app.mount("#app");
复制代码


2.1 创建store


//sbinsrc/store/user.tsimport { defineStore } from 'pinia'
// 第一个参数是应用程序中 store 的唯一 idexport const useUsersStore = defineStore('users', { // 其它配置项})
复制代码


创建store很简单,调用 pinia中的defineStore函数即可,该函数接收两个参数:


  • name:一个字符串,必传项,该store的唯一id

  • options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。


我们可以定义任意数量的store,因为我们其实一个store就是一个函数,这也是pinia的好处之一,让我们的代码扁平化了,这和Vue3的实现思想是一样的


2.2 使用store


<!-- src/App.vue --><script setup lang="ts">import { useUsersStore } from "../src/store/user";const store = useUsersStore();console.log(store);</script>
复制代码


2.3 添加state


export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 20,      sex: "男",    };  },});
复制代码


2.4 读取state数据


<template>  <img alt="Vue logo" src="./assets/logo.png" />  <p>姓名:{{ name }}</p>  <p>年龄:{{ age }}</p>  <p>性别:{{ sex }}</p></template><script setup lang="ts">import { ref } from "vue";import { useUsersStore } from "../src/store/user";const store = useUsersStore();const name = ref<string>(store.name);const age = ref<number>(store.age);const sex = ref<string>(store.sex);</script>
复制代码


上段代码中我们直接通过store.age等方式获取到了store存储的值,但是大家有没有发现,这样比较繁琐,我们其实可以用解构的方式来获取值,使得代码更简洁一点


import { useUsersStore, storeToRefs } from "../src/store/user";const store = useUsersStore();const { name, age, sex } = storeToRefs(store); // storeToRefs获取的值是响应式的
复制代码


2.5 修改state数据


<template>  <img alt="Vue logo" src="./assets/logo.png" />  <p>姓名:{{ name }}</p>  <p>年龄:{{ age }}</p>  <p>性别:{{ sex }}</p>  <button @click="changeName">更改姓名</button></template><script setup lang="ts">import child from './child.vue';import { useUsersStore, storeToRefs } from "../src/store/user";const store = useUsersStore();const { name, age, sex } = storeToRefs(store);const changeName = () => {  store.name = "张三";  console.log(store);};</script>
复制代码


2.6 重置state


  • 有时候我们修改了state数据,想要将它还原,这个时候该怎么做呢?就比如用户填写了一部分表单,突然想重置为最初始的状态。

  • 此时,我们直接调用store$reset()方法即可,继续使用我们的例子,添加一个重置按钮


<button @click="reset">重置store</button>// 重置storeconst reset = () => {  store.$reset();};
复制代码


当我们点击重置按钮时,store中的数据会变为初始状态,页面也会更新


2.7 批量更改state数据


如果我们一次性需要修改很多条数据的话,有更加简便的方法,使用store$patch方法,修改app.vue代码,添加一个批量更改数据的方法


<button @click="patchStore">批量修改数据</button>// 批量修改数据const patchStore = () => {  store.$patch({    name: "张三",    age: 100,    sex: "女",  });};
复制代码


  • 有经验的小伙伴可能发现了,我们采用这种批量更改的方式似乎代价有一点大,假如我们state中有些字段无需更改,但是按照上段代码的写法,我们必须要将 state 中的所有字段例举出了。

  • 为了解决该问题,pinia提供的$patch方法还可以接收一个回调函数,它的用法有点像我们的数组循环回调函数了。


store.$patch((state) => {  state.items.push({ name: 'shoes', quantity: 1 })  state.hasChanged = true})
复制代码


2.8 直接替换整个state


pinia提供了方法让我们直接替换整个state对象,使用store$state方法


store.$state = { counter: 666, name: '张三' }
复制代码


上段代码会将我们提前声明的state替换为新的对象,可能这种场景用得比较少


  1. getters属性


  • gettersdefineStore参数配置项里面的另一个属性

  • 可以把getter想象成Vue中的计算属性,它的作用就是返回一个新的结果,既然它和Vue中的计算属性类似,那么它肯定也是会被缓存的,就和computed一样


3.1 添加getter


export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 10,      sex: "男",    };  },  getters: {    getAddAge: (state) => {      return state.age + 100;    },  },})
复制代码


上段代码中我们在配置项参数中添加了getter属性,该属性对象中定义了一个getAddAge方法,该方法会默认接收一个state参数,也就是state对象,然后该方法返回的是一个新的数据


3.2 使用getter


<template>  <p>新年龄:{{ store.getAddAge }}</p>  <button @click="patchStore">批量修改数据</button></template><script setup lang="ts">import { useUsersStore } from "../src/store/user";const store = useUsersStore();// 批量修改数据const patchStore = () => {  store.$patch({    name: "张三",    age: 100,    sex: "女",  });};</script>
复制代码


上段代码中我们直接在标签上使用了store.gettAddAge方法,这样可以保证响应式,其实我们state中的name等属性也可以以此种方式直接在标签上使用,也可以保持响应式


3.3 getter中调用其它getter


export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 20,      sex: "男",    };  },  getters: {    getAddAge: (state) => {      return state.age + 100;    },    getNameAndAge(): string {      return this.name + this.getAddAge; // 调用其它getter    },  },});
复制代码


3.3 getter传参


export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 20,      sex: "男",    };  },  getters: {    getAddAge: (state) => {      return (num: number) => state.age + num;    },    getNameAndAge(): string {      return this.name + this.getAddAge; // 调用其它getter    },  },});
复制代码


<p>新年龄:{{ store.getAddAge(1100) }}</p>
复制代码


  1. actions属性


  • 前面我们提到的stategetters 属性都主要是数据层面的,并没有具体的业务逻辑代码,它们两个就和我们组件代码中的data数据和computed计算属性一样。

  • 那么,如果我们有业务代码的话,最好就是卸载actions属性里面,该属性就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。

  • actions属性值同样是一个对象,该对象里面也是存储的各种各样的方法,包括同步方法和异步方法


4.1 添加actions


export const useUsersStore = defineStore("users", {  state: () => {    return {      name: "test",      age: 20,      sex: "男",    };  },  getters: {    getAddAge: (state) => {      return (num: number) => state.age + num;    },    getNameAndAge(): string {      return this.name + this.getAddAge; // 调用其它getter    },  },  actions: {    // 在实际场景中,该方法可以是任何逻辑,比如发送请求、存储token等等。大家把actions方法当作一个普通的方法即可,特殊之处在于该方法内部的this指向的是当前store    saveName(name: string) {      this.name = name;    },  },});
复制代码


4.2 使用actions


使用actions中的方法也非常简单,比如我们在App.vue中想要调用该方法


const saveName = () => {  store.saveName("poetries");};
复制代码


总结


pinia的知识点很少,如果你有 Vuex 基础,那么学起来更是易如反掌


pinia 无非就是以下 3 个大点:


  • state

  • getters

  • actions

Vue 性能优化

编码优化


  • 事件代理

  • keep-alive

  • 拆分组件

  • key 保证唯一性

  • 路由懒加载、异步组件

  • 防抖节流


Vue 加载性能优化


  • 第三方模块按需导入( babel-plugin-component

  • 图片懒加载


用户体验


  • app-skeleton 骨架屏

  • shellap p 壳

  • pwa


SEO 优化


  • 预渲染

谈谈对 keep-alive 的了解

keep-alive 可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。常用的2个属性 include/exclude ,2个生命周期 activated deactivated

了解 nextTick 吗?

异步方法,异步渲染最后一步,与 JS 事件循环联系紧密。主要使用了宏任务微任务(setTimeoutpromise那些),定义了一个异步方法,多次调用nextTick会将方法存入队列,通过异步方法清空当前队列。

Vue 的优点

  • 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb

  • 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;

  • 双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单;

  • 组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势;

  • 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;

  • 虚拟 DOM:dom 操作是非常耗费性能的,不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;

  • 运行速度更快:相比较于 react 而言,同样是操作虚拟 dom,就性能而言, vue 存在很大的优势。

如何从真实 DOM 到虚拟 DOM

涉及到 Vue 中的模板编译原理,主要过程:


  1. 将模板转换成 ast 树, ast 用对象来描述真实的 JS 语法(将真实 DOM 转换成虚拟 DOM)

  2. 优化树

  3. ast 树生成代码

Vue3.0 有什么更新

(1)监测机制的改变


  • 3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。

  • 消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:


(2)只能监测属性,不能监测对象


  • 检测属性的添加和删除;

  • 检测数组索引和长度的变更;

  • 支持 Map、Set、WeakMap 和 WeakSet。


(3)模板


  • 作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。

  • 同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。


(4)对象式的组件声明方式


  • vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。

  • 3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易


(5)其它方面的更改


  • 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。

  • 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。

  • 基于 tree shaking 优化,提供了更多的内置功能。

v-on 可以监听多个方法吗?

可以监听多个方法


<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />
复制代码


v-on 常用修饰符


  • .stop 该修饰符将阻止事件向上冒泡。同理于调用 event.stopPropagation() 方法

  • .prevent 该修饰符会阻止当前事件的默认行为。同理于调用 event.preventDefault() 方法

  • .self 该指令只当事件是从事件绑定的元素本身触发时才触发回调

  • .once 该修饰符表示绑定的事件只会被触发一次


用户头像

bb_xiaxia1998

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
一文梳理vue面试题知识点_Vue_bb_xiaxia1998_InfoQ写作社区