vue面试题

547 阅读10分钟

如何理解mvvm原理

mvvm分为 model view viewmodel 这三部分,就是一个观察监听的响应式系统 在vue init时 会通过Object.defineProperty 来进行getter setter 进行绑定,当对象被读取时会进行getter方法,从而进行依赖收集 依赖收集是把当前的watcher存放到Depsubs中,当对象被修改时 会触发setter 来通知Depsubs中的每个watcher,告诉watcher值变了,需要重新 渲染视图 这是watcher会去调用update来更新视图

响应式数据的原理是什么

响应式原理核心就是观察者模式 vue初始化时会去执行observe,传入对象,遍历对象的所有属性,然后执行defineReactive(obj, key, val),在vue初始化时会去执行observe,传入对象,遍历对象的所以属性,然后执行defineReactive函数中会创建会Dep(),dep是用来存放watcher对象的,watcher对象是用来通知更新数据的,当对象被读取时会进行getter方法 把watcher对象存入dep中,当对象被修改时会触发setter方法 来通知dep中每个watcher,然后watcher去执行update来更新视图

vue中是如何检测数组变化的

为什么不能直接检测到数组的变化

因为用Object.defineProperty为对象数组添加getter setter属性后,当数组执行自身的一些方法如push pop等并不会触发setter, 所以这是要重写数组方法的原因 这个在源码observe/index.js中有写到

// 获取数组的原型Array.prototype,上面有我们常用的数组方法
const arrayProto = Array.prototype
// 创建一个空对象arrayMethods,并将arrayMethods的原型指向Array.prototype
export const arrayMethods = Object.create(arrayProto);

// 列出需要重写的数组方法名
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
// 遍历上述数组方法名,依次将上述重写后的数组方法添加到arrayMethods对象上
methodsToPatch.forEach(function (method) {
  // 保存一份当前的方法名对应的数组原始方法
  const original = arrayProto[method]
  // 将重写后的方法定义到arrayMethods对象上,function mutator() {}就是重写后的方法
  def(arrayMethods, method, function mutator (...args) {
    // 调用数组原始方法,并传入参数args,并将执行结果赋给result
    const result = original.apply(this, args)
    // 当数组调用重写后的方法时,this指向该数组,当该数组为响应式时,就可以获取到其__ob__属性
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // 将当前数组的变更通知给其订阅者
    ob.dep.notify()
    // 最后返回执行结果result
    return result
  })
})

为何vue采用异步渲染

如果不采用异步渲染,比如我们循环1000次,每次都会去触发setter方法 最后去修改真实dom,这样dom会更新1000次,性能太低了

怎么解决呢

vue每次触发某个数据的setter方法后,对应的watcher会被push到一个queue中,在下一个tick的时候想这个队列里的watcher全部拿出来run一下(watcher对象的一个方法 用来触发patch操作) 什么是tike呢 就是nexttTike vue实现了一个nexttTike函数 传入一个cb,这个cb会存储到一个队列中,在下一个tick时触发队列中的所有cb事件,因为浏览器平台没有实现nextTick方法,所以vue源码中用了Promise setTimeout setImmediate等方式在microtask中创建一个事件,

nextTick的实现原理

nextTick就是异步更新原理,vue检测到数据变化时并不是直接去更新dom 而是开启一个队列,缓存所有所有数据改变的对象watcher,在缓存时会通过patch去除重复数据,从而避免不必要的计算和dom操作,然后在下一个tick中去执行 队列里的工作,vue会根据浏览器环境优先选择PromiseMutationObserver 如果都不支持 那会选择setTimeout 目的都是延迟执行函数 等到dom更新后在使用

let callbacks = []; //首先定义一个 callbacks 数组用来存储 nextTick,在下一个 tick 处理这些回调函数之前,所有的 cb 都会被存在这个 callbacks 数组中
let pending = false; //pending 是一个标记位,代表一个等待的状态
function nextTick (cb) {
    callbacks.push(cb);
    if (!pending) {
        pending = true;
        setTimeout(flushCallbacks, 0);
    }
}

//setTimeout 会在 task 中创建一个事件 flushCallbacks ,flushCallbacks 则会在执行时将 callbacks 中的所有 cb 依次执行
function flushCallbacks () {
    pending = false;
    //  拷贝出函数数组副本
    const copies = callbacks.slice(0);
    // 清空数组
    callbacks.length = 0;
    // 依次执行函数
    for (let i = 0; i < copies.length; i++) {
      //函数中就就是Watcher对象执行的函数 更新视图
        copies[i]();
    }
}

vu组件的生命周期

--创建期间的生命周期函数

beforeCreate: 实例刚刚创建好 此时还没有初始化data和methods

created: 实例创建完毕,data和methods也已经创建好,但是还没有开始编译模板

beforeMount: 此时已经编译好了模板 但是还没有挂载到页面上

mounted: 已经将编译好的模板挂载到了页面上了

--运行期间的生命周期函数

beforeUpdate: 状态更新前执行的函数 此时data中的数据是最新的 但是dom上还是旧的数据 因为还没开始渲染dom节点

updated: 此时data和dom上都是最新的数据了 界面已经被重新渲染好了

--销毁期间的生命周期函数

beforeDestroy: 实例被销毁前调用 在函数中是实例仍然可以使用

destroyed: 实例被销毁

Ajax请求放在哪个生命周期

看实际情况 一般created里面就可以了 如果涉及到页面加载完成后调用的话 就可以放在mounted

vue父子组件生命周期调用顺序

父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父子mounted

vue中Computed的特点

computed 是计算属性 特点是基于依赖缓存的,而方法methods不会进行缓存的 所以性能方面computed会更好

watch中的deep:true是如何实现的 (比较浅)

watch的deep就是深度监听
比如监听了一个对象obj: {a: '2'} ,他是监听不到对象内属性的变化的,如果要监听 需要设置deep:true 这样watch就会层层遍历下去 给对象的属性添加上gettersetter方法

vue事件绑定的原理 (比较浅)

事件绑定是用v-on:事件名=函数名来完成的

vue中 v-html 会导致哪些问题

v-html更新的是innerHTML 不会作为vue的模板进行编译 很容易导致xss攻击

vue中v-show跟v-if有什么区别

v-if 条件切换时会对标签进行创建和销毁 v-show仅在初始化时加载一次

为什么v-if 和 v-for不能连用

因为v-forv-if优先 所以每次v-for的时候都会只需v-if 影响性能,必要时用computed

组件中的data为什么是一个函数

var Component= function() {}
Component.prototype.data = {
  a: 1,
  b: 2
}
var com1 = new Component()
var com2 = new Component()
com1.data.a = 2
console.log(com2.data.a) //com2.data.a会变为2

因为使用组件本质上是创建了组件的引用,使用组件时 data是创建在构造器的原型链上的,但是实例化的组件1和组件2会共享一份data对象,当你修改其中一个data对象后,另一个的对象的值也会随着改变,当data是一个函数后,每一个组件实例的data都是独立的,不会相互影响了

vue组件如何通信

1.props

2.中央事件总线$on $emit,

3.v-mode: v-model="total" 直接绑定变量 total。接着在子组件中,在 $emit 方法传入事件名 input,这样 Vue.js 就会自动找到 v-model 绑定的变量

4.父子链: this.$parent来访问父组件实例,this.$children 来访问它的所有子组件实例

5.ref

什么是作用域插槽

子组件的作用域插槽可以为父组件的插槽显示提供数据

子组件 文件名slot.vue

<template>  
  <div>  
    <slot name="a" :myData="user"></slot>  
    <br/>  
    <slot name="b"></slot>  
    <br/>  
  </div>  
</template>

<script>
data() {  
  return {  
    user: "cb"  
  }  
}
</script>

父组件:

<div>  
   <Slot v-slot:a="slotProps">{{ slotProps.myData }}</Slot>   //slotProps这个就是子组件定义的data
</div>
//输出 "cb"

用VNode 来描述一个dom结构

VNode 其实就是一棵以 JavaScript 对象(VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象,最终通过一些列的操作最终映射到真实的dom上,所以会具备跨平台的能力 创建一个VNode需要一些属性,比如当前的

当前节点的标签名(tag)

当前节点的一些数据信息(data:{props,attrs等})

当前节点的子节点(chilred,就是个数组)

当前节点的文本(text)

当前虚拟节点对应的真实dom节点(elm)

diff算法的时间复杂度

时间复杂度只有 O(n)

简书vue diff算法原理 (浅)

diff算法是通过比较新老同层的树节点 而不是逐层搜索遍历的方式,所以时间复杂度只有 O(n),是一种相当高效的算法

v-for 为什么要用key

要用key来做唯一标识 在diff算法中更快找到节点 提高diff速度

描述组件渲染和更新过程

组件渲染:

渲染的过程分为三个阶段 parse(解析)、optimize(优化)、generate(生成),

第一步parse:parse会根据正则将template模板中的字符串进行解析 形成AST(AST就是抽象语法树),

第二步optimize:因为在patch过程中是将VNode一层层进行比较,然后将差异更新在视图上,如果一些静态节点是不需要根据数据变化而产生变化的,这些节点就没必要去对比,是可以跳过的,从而可以节省一些性能,所以经过optimize这层处理,会给每个节点加上static属性,用来标记是否是静态,

第三步generate:generate会将 AST 转化成 render funtion 字符串,经历过这些过程以后,我们已经把 template 顺利转成了 render function了。

更新:

在操作对象时会执行setter方法 触发对应的Dep中的watcher对象,watcher对象会调用对应的update来修改视图,最终是将新产生的VNode和老VNode进行一个patch的过程,最终将这些差异更新到视图上

vue中模板编译原理 (浅)

第一步 parse:把模板字符串转为ATS

第二步 optimize:是对AST进行静态节点标记,主要用来做虚拟DOM的渲染优化

第三步 generate:是 使用 AST 生成 render 函数代码字符串

为什么要使用异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块,优化应用程序,使其变得更快

官方文档: cn.vuejs.org/v2/guide/co…

谈谈对keep-alive的理解

keep-alive的作用

  • 使用动态组件时,能在组件切换过程中保留状态在内存中,防止重复渲染dom
  • keep-alive只能有一个组件被渲染,不能与v-for一起使用。

属性

  • include:字符串或正则表达式,只有名称匹配的组件会被缓存
  • exclude: 字符串或正则表达式,名称匹配的组件不会被缓存
  • max:数字,最多可以缓存多少组件实例

生命周期

  • activated和deactivated