Vue、Vuex、Vue-Router 面试题大全

Vue面试题

Vue

1.请详细说下你对 vue 生命周期的理解?

总共分为 8 个阶段创建前 / 后,载入前 / 后,更新前 / 后,销毁前 / 后。

创建前 / 后: 在 beforeCreate 阶段,vue 实例的挂载元素 el 和数据对象 data 都为 undefined,还未初始化。在 created 阶段,vue 实例的数据对象 data 有了,el 为 undefined,还未初始化。

载入前 / 后:在 beforeMount 阶段,vue 实例的 $el 和 data 都初始化了,但还是挂载之前为虚拟的 dom 节点,data.message 还未替换。在 mounted 阶段,vue 实例挂载完成,data.message 成功渲染。

更新前 / 后:当 data 变化时,会触发 beforeUpdate 和 updated 方法

销毁前 / 后:在执行 destroy 方法后,对 data 的改变不会再触发周期函数,说明此时 vue 实例已经解除了事件监听以及和 dom 的绑定,但是 dom 结构依然存在

2.为什么 vue 组件中 data 必须是一个函数?

对象为引用类型,当复用组件时,由于数据对象都指向同一个 data 对象,当在一个组件中修改 data 时,其他重用的组件中的 data 会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object 的实例),引用地址不同,则不会出现这个问题。

3.vue 中 v-if 和 v-show 有什么区别?

v-if 和 v-show 看起来似乎差不多,当条件不成立时,其所对应的标签元素都不可见,但是这两个选项是有区别的:

1、v-if 在条件切换时,会对标签进行适当的创建和销毁,而 v-show 则仅在初始化时加载一次,因此 v-if 的开销相对来说会比 v-show 大。

2、v-if 是惰性的,只有当条件为真时才会真正渲染标签;如果初始条件不为真,则 v-if 不会去渲染标签。v-show 则无论初始条件是否成立,都会渲染标签,它仅仅做的只是简单的 CSS 切换。

4.computed 和 watch 的区别

计算属性 computed:

  • 支持缓存,只有依赖数据发生改变,才会重新进行计算
  • 不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化
  • computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于 data 中声明过或者父组件传递的 props 中的数据通过计算得到的值
  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用 computed
  • 如果 computed 属性属性值是函数,那么默认会走 get 方法;函数的返回值就是属性的属性值;在 computed 中的,属性都有一个 get 和一个 set 方法,当数据变化时,调用 set 方法。

侦听属性 watch:

  • 不支持缓存,数据变,直接会触发相应的操作;
  • watch 支持异步;
  • 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
  • 当一个属性发生变化时,需要执行对应的操作;一对多;
  • 监听数据必须是 data 中声明过或者父组件传递过来的 props 中的数据,当数据变化时,触发其他操作,函数有两个参数:

immediate:组件加载立即触发回调函数执行

watch: {
  firstName: {
    handler(newName, oldName) {
      this.fullName = newName + ' ' + this.lastName;
    },
    // 代表在wacth里声明了firstName这个方法之后立即执行handler方法
    immediate: true
  }
}
复制代码

deep: deep 的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改 obj 里面任何一个属性都会触发这个监听器里的 handler

watch: {
  obj: {
    handler(newName, oldName) {
      console.log('obj.a changed');
    },
    immediate: true,
    deep: true
  }
}
复制代码

优化:我们可以使用字符串的形式监听

watch: {
  'obj.a': {
    handler(newName, oldName) {
      console.log('obj.a changed');
    },
    immediate: true,
    // deep: true
  }
}
复制代码

这样 Vue.js 才会一层一层解析下去,直到遇到属性 a,然后才给 a 设置监听函数。

5.vue-loader 是什么?使用它的用途有哪些?

vue 文件的一个加载器,跟 template/js/style 转换成 js 模块。

6.$nextTick 是什么?

vue 实现响应式并不是数据发生变化后 dom 立即变化,而是按照一定的策略来进行 dom 更新。

nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 nextTick,则可以在回调中获取更新后的 DOM

7.v-for key 的作用

当 Vue 用 v-for 正在更新已渲染过的元素列表是,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue 将不是移动 DOM 元素来匹配数据项的改变,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。key 属性的类型只能为 string 或者 number 类型。

key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复 / 再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

8.Vue 的双向数据绑定原理是什么?

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 变更的双向绑定效果。

9.请说下封装 vue 组件的过程

首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。

然后,使用 Vue.extend 方法创建一个组件,然后使用 Vue.component 方法注册组件。子组件需要数据,可以在 props 中接受定义。而子组件修改好数据后,想把数据传递给父组件。可以采用 emit 方法。

10.Vue.js 的 template 编译

简而言之,就是先转化成 AST 树,再得到的 render 函数返回 VNode(Vue 的虚拟 DOM 节点),详细步骤如下:

首先,通过 compile 编译器把 template 编译成 AST 语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile 是 createCompiler 的返回值,createCompiler 是用以创建编译器的。另外 compile 还负责合并 option。

然后,AST 会经过 generate(将 AST 语法树转化成 render funtion 字符串的过程)得到 render 函数,render 的返回值是 VNode,VNode 是 Vue 的虚拟 DOM 节点,里面有(标签名、子节点、文本等等)

11.vue 如何监听对象或者数组某个属性的变化

当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为 Object.defineProperty () 限制,监听不到变化。

解决方式:

  • this.$set (你要改变的数组 / 对象,你要改变的位置 /key,你要改成什么 value)
this.$set(this.arr, 0, "OBKoro1"); // 改变数组
this.$set(this.obj, "c", "OBKoro1"); // 改变对象
复制代码
  • 调用以下几个数组的方法
splice()、 push()、pop()、shift()、unshift()、sort()、reverse()
复制代码

vue 源码里缓存了 array 的原型链,然后重写了这几个方法,触发这几个方法的时候会 observer 数据,意思是使用这些方法不用我们再进行额外的操作,视图自动进行更新。 推荐使用 splice 方法会比较好自定义,因为 splice 可以在数组的任何位置进行删除 / 添加操作

12.常用的事件修饰符

  • .stop: 阻止冒泡
  • .prevent: 阻止默认行为
  • .self: 仅绑定元素自身触发
  • .once: 2.1.4 新增,只触发一次
  • passive: 2.3.0 新增,滚动事件的默认行为 (即滚动行为) 将会立即触发,不能和.prevent 一起使用
  • .sync 修饰符

从 2.3.0 起 vue 重新引入了.sync 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。示例代码如下:


复制代码

会被扩展为:


复制代码

当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:

this.$emit('update:foo', newValue)
复制代码

13.vue 如何获取 dom

先给标签设置一个 ref 值,再通过 this.$refs.domName 获取,例如:

const dom = this.$refs.test 复制代码

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

是可以的,来个例子:


复制代码

15.assets 和 static 的区别

这两个都是用来存放项目中所使用的静态资源文件。

两者的区别:

assets 中的文件在运行 npm run build 的时候会打包,简单来说就是会被压缩体积,代码格式化之类的。打包之后也会放到 static 中。

static 中的文件则不会被打包。

建议:将图片等未处理的文件放在 assets 中,打包减少体积。而对于第三方引入的一些资源文件如 iconfont.css 等可以放在 static 中,因为这些文件已经经过处理了。

16.slot 插槽

很多时候,我们封装了一个子组件之后,在父组件使用的时候,想添加一些 dom 元素,这个时候就可以使用 slot 插槽了,但是这些 dom 是否显示以及在哪里显示,则是看子组件中 slot 组件的位置了。

17.vue 初始化页面闪动问题

使用 vue 开发时,在 vue 初始化之前,由于 div 是不归 vue 管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于 { {message}} 的字样,虽然一般情况下这个时间很短暂,但是我们还是有必要让解决这个问题的。

首先:在 css 里加上以下代码

[v-cloak] {
    display: none;
}
复制代码

如果没有彻底解决问题,则在根元素加上 style=“display: none;” :style="{display: ‘block’}"

18. Vue 中操作 data 中数组的方法中哪些可以触发视图更新,哪些不可以,不可以的话有什么解决办法?

push ()、pop ()、shift ()、unshift ()、splice ()、sort ()、reverse () 这些方法会改变被操作的数组; filter ()、concat ()、slice () 这些方法不会改变被操作的数组,返回一个新的数组; 以上方法都可以触发视图更新。

  • 利用索引直接设置一个数组项,例:this.array[index] = newValue
  • 直接修改数组的长度,例:this.array.length = newLength

以上两种方法不可以触发视图更新;

  • 可以用 this.$set(this.array,index,newValue)this.array.splice(index,1,newValue) 解决方法 1
  • 可以用 this.array.splice(newLength) 解决方法 2

19.混入(mixin)

  • 全局混入在项目中怎么用?

    在 main.js 中写入

        import Vue from 'vue';
        import mixins from './mixins';
        Vue.mixin(mixins);
    

    之后,全局混入可以写在 mixins 文件夹中 index.js 中,全局混入会影响到每一个之后创建的 Vue 实例(组件);

  • 局部混入在项目中怎么用

    局部混入的注册,在 mixins 文件中创建一个 a_mixin.js 文件,然后再 a.vue 文件中写入

    
    

    局部混入只会影响 a.vue 文件中创建的 Vue 实例,不会影响到其子组件创建的 Vue 实例;

  • 组件的选项和混入的选项是怎么合并的

    • 数据对象【data 选项】,在内部进行递归合并,并在发生冲突时以组件数据优先;
    • 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用;
    • watch 对象合并时,相同的 key 合成一个对象,且混入监听在组件监听之前调用;
    • 值为对象的选项【filters 选项、computed 选项、methods 选项、components 选项、directives 选项】将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

20.computed 中的属性名和 data 中的属性名可以相同吗?

不能同名,因为不管是 computed 属性名还是 data 数据名还是 props 数据名都会被挂载在 vm 实例上,因此这三个都不能同名。

if (key in vm.$data) {
    warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
    warn(`The computed property "${key}" is already defined as a prop.`, vm)
}

21.怎么强制刷新组件?

  • this.$forceUpdate()。
  • 组件上加上 key,然后变化 key 的值。

22.watch 的属性和 methods 方法能用箭头函数定义吗?

不可以。this 会是 undefind, 因为箭头函数中的 this 指向的是定义时的 this,而不是执行时的 this,所以不会指向 Vue 实例的上下文。

23.给组件绑定自定义事件无效怎么解决?

加上修饰词.native。

24.怎么访问子组件的实例或者子元素?

先用 ref 特性为子组件赋予一个 ID 引用

  • 比如子组件有个 focus 的方法,可以这样调用 this.$refs.myInput.focus()
  • 比如子组件有个 value 的数据,可以这样使用 this.$refs.myInput.value

先用 ref 特性为普通的 DOM 元素赋予一个 ID 引用

  • 第一个li
  • 第一个li
console.log(this.$refs['mydiv'].getElementsByClassName('item')[0].innerHTML)//第一个li

25.怎么在子组件中访问父组件的实例?怎么在组件中访问到根实例?

使用 this.$parent 来访问

this.$root

26.组件会在什么时候下被销毁?

  • 没有使用 keep-alive 时的路由切换;
  • v-if='false'
  • 执行 vm.$destroy()

27.is 这个特性你有用过吗?主要用在哪些方面?

  • 动态组件

componentName 可以是在本页面已经注册的局部组件名和全局组件名,也可以是一个组件的选项对象。 当控制 componentName 改变时就可以动态切换选择组件。

  • is 的用法

有些 HTML 元素,诸如

      、 和 ,只能出现在其它某些特定的元素内部。

      所以上面 会被作为无效的内容提升到外部,并导致最终渲染结果出错。应该这么写:

      28.prop 验证的 type 类型有哪几种?

      String、Number、Boolean、Array、Object、Date、Function、Symbol, 此外还可以是一个自定义的构造函数 Personnel,并且通过 instanceof 来验证 propwokrer 的值是否是通过这个自定义的构造函数创建的。

      function Personnel(name,age){
           
          this.name = name;
          this.age = age;
      }
      export default {
           
          props:{
           
              wokrer:Personnel
          }
      }
      

      29.在 Vue 事件中传入 $event ,使用 $event.target和 event.currentTarget 有什么区别?

      $event.currentTarget 始终指向事件所绑定的元素,而 $event.target 指向事件发生时的元素。

      30.使用事件修饰符要注意什么?

      要注意顺序很重要,用 @click.prevent.self 会阻止所有的点击,而 @click.self.prevent 只会阻止对元素自身的点击。

      31.说说你对 Vue 的表单修饰符.lazy 的理解?

      input 标签 v-model 用 lazy 修饰之后,并不会立即监听 input 的 value 的改变,会在 input 失去焦点之后,才会监听 input 的 value 的改变。

      32.v-once 的使用场景有哪些?

      其作用是只渲染元素和组件一次。随后的重新渲染,元素 / 组件及其所有的子节点将被视为静态内容并跳过。故当组件中有大量的静态的内容可以使用这个指令。

      33.v-cloak 和 v-pre 有什么作用?

      v-cloak:可以解决在页面渲染时把未编译的 Mustache 标签({ {value}})给显示出来。

      [v-cloak] {
          display: none!important;
      }
      
      { { message }}

      v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。

      {
          { this will not be compiled }}
      

      34.怎么使 css 样式只在当前组件中生效?在 style 上加 scoped 属性需要注意哪些?你知道 style 上加 scoped 属性的原理吗?

      
      
      • 如果在公共组件中使用,修改公共组件的样式需要用 /deep/

      • vue 通过在 DOM 结构以及 css 样式上加上唯一的标记 data-v-xxxxxx,保证唯一,达到样式私有化,不污染全局的作用。

      35.Vue 渲染模板时怎么保留模板中的 HTML 注释呢?

      • 在组件中将 comments 选项设置为 true