vue面试题

360 阅读15分钟

1.如何理解MVVM原理

	M: Model(模型), vue中是data(为view提供数据)
	V: View(视图), vue中是模板页面(显示data中的数据)
	VM: ViewModel(视图模型), vue中是Vue实例对象(管理者: 数据绑定/DOM监听) 

2.响应式数据的原理是什么

	1、把一个普通的js对象传给Vue实例的data选项对象
	2、Vue将遍历此对象的所有的属性,并使用Object.defineProperty把这些属性全部转换为getter/setter
	3、Vue内部会对数据进行劫持操作,进而追踪依赖,在属性被访问和修改时通知变化

3.Vue中是如何检测数组变化

	数组: 重写数组更新数组元素的方法, 只要调用数组的这些方法, 就会更新相应的界面
	对象: 对对象中的属性进行setter监视, 只要设置了新的属性值, 就会更新相应的界面

4.为何Vue采用异步渲染

    因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,
    所以为了性能考虑。Vue会在本轮数据数据更新后,在去更新视图!

5.nextTick实现原理

    用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
    原理:vue用异步队列的方式来控制DOM更新和nextTick回调先后执行,每次在回调队列的末尾添加一个task

6.Vue组件的生命周期

    利用keep-alive缓存了组件之后,再次进入组件不会触发
    beforeCreate、created 、beforeMount、mounted,
    如果你想每次进入组件都做一些事情的话,你可以放在activated进入缓存组件的钩子中。
    同理:离开缓存组件的时候,beforeDestroy和destroyed并不会触发,
    可以使用deactivated离开缓存组件的钩子来代替。

7.Ajax请求放在哪个生命周期

    created或者mounted

8.何时需要使用beforeDestory

    清除定时器,做收尾工作

9.Vue父子组件生命周期调用顺序

    加载渲染过程
        父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
    子组件更新过程
        父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
    父组件更新过程
        父 beforeUpdate -> 父 updated
    销毁过程
        父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

10.Vue中computed的特点

    是计算属性,依赖其它属性值,并且 computed 的值有缓存,
    只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

    computed与watch的差异
        computed是计算一个新的属性,并将该属性挂载到vm上(Vue实例),
        watch是监听已经存在且已经挂载到vm实例上的数据,所以watch同样可以监听computed计算属性的变化
        computed本质上是一个惰性求职的观察者,具有缓存性,只有当依赖变化后,
        第一次访问computed属性,才会计算新的值,而watch是数据发生变化便会调用执行函数
        从使用场景来说,computed适用于一个数据被多个数据影响,而watch适用于一个数据影响多个数据

    computed实现原理
        computed 本质是一个惰性求值的观察者。
        computed内部实现了一个惰性的watcher,也就是computed watcher,computed watcher不会立刻求值,
        同时拥有一个dep实例
        其内部通过this.dirty属性标记计算属性是否需要重新求值
        当computed依赖状态发生改变时,就会通知这个computed watcher
        computed watcher通过this.dep.subs.length判断有没有订阅者
        如果有,会重新计算,然后对比新旧值,如果值发生变化,就会重新渲染(Vue确保不只是依赖的值发生变化,
        而是最终计算的值发生变化,才会重新渲染)
        如果没有,仅仅把this.dirty = true
        (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后需要读取属性的时候,才会真正计算,它具备lazy(懒计算特性))

11.Watch中deep:true是如何实现的

    watch 初始化的时候会先读取一遍监听数据的值,把数据收集到watch的watcher中,
    当数据发生改变时,通知watcher,更新数据,
    Vue通过Object.defineProperty给【值是对象的属性】 设置 set 和 get的时候
        如果你直接改变或读取这个属性 ( 直接赋值 ),可以触发这个属性的设置的 set 和 get
        但是你改变或读取它的内部属性时,并不会触发set和get
    如果你设置了deep:true,它就会递归遍历这个值(对象),把对象内所有的属性值收集到watch的watcher中

12.Vue中数据绑定的原理

数据劫持(监视)和发布-订阅模式
    a.在observer中, 通过Object.defineProperterty()给data中所有属性添加setter/getter, 实现数据劫持
    b.为每个data中的属性创建一个对应的dep对象(订阅器)
    c.为模板中的每个表达式创建对应的watcher对象(订阅者), 并关联到对应的dep上
    d.一旦data中的数据发生变化,setter(发布者)会通过dep对象通知所有关联的watcher,
      watcher收到通知后就更新对应的节点

13.Vue中v-html会导致哪些问题

    V-html更新的是元素的 innerHTML,内容按普通 HTML 插入,
    不会作为Vue模板进行编译,有的时候我们需要渲染的html片段中有插值表达式,并不会被解析
    解决方法: 如果存在事件和插值语法,可以使用组件代替
    在单文件组件里,scoped 的样式不会应用在 v-html 内部,因为那部分 HTML 没有被 Vue 的模板编译器处理
    解决方法: 可以重新定义一个style

14.Vue中v-if和v-show的区别

    v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;
    也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
    v-show,就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS的“display”属性
    进行切换。
    所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;
    v-show 则适用于需要非常频繁切换条件的场景。

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

    v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候
    可以使用computed属性,用v-for来循环通过computed计算过滤后的对象

16.v-model中的实现原理及如何自定义v-model

	v-model其实是一个语法糖,它会自动的在元素或者组件上面解析为 :value="" 和 @input=""
	自定义v-model
	    一般都是给自己写的组件做自定义v-model,例如:<my-com v-model="变量"><my-com>
	    将变量的值与自定义组件中的props: ["value"]定义的value进行数据绑定,props的value获得了变量值
	    在自定义组件中通过$emit("input",我们想要传递的变量)来手动触发input事件,
	    将$emit的第二个参数值传给<my-com v-model="变量"><my-com>中的变量

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

	因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,
	那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 
	选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,
	组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,
	是不会被复用的,因此不存在引用对象的问题。

18.Vue组件如何通信

    1). props
        父子组件间通信的基本方式
        属性值的2大类型: 
            一般: 父组件-->子组件
            函数: 子组件-->父组件
        隔层组件间传递: 必须逐层传递(麻烦)
        兄弟组件间: 必须借助父组件(麻烦)
    2). vue自定义事件
        方式1: 给子组件标签绑定事件监听
            子组件向父组件的通信方式
            功能类似于function props
            不适合隔层组件和兄弟组件间的通信
        方式2: 通过单独的vm对象绑定监听/分发事件
            任意组件间通信
    3). 消息订阅和发布(pubsub-js)
        适合于任何关系的组件间通信
        缺点: 管理不够集中
    4). vuex
        多组件共享状态(数据的管理)
        组件间的关系也没有限制
        功能比pubsub强大, 更适用于vue项目
    5). slot
        父向子通信
        通信是带数据的标签
        注意: 标签是在父组件中解析

19.什么是作用域插槽

    父组件在用子组件来填充插槽的时候 有时候需要用到子组件里面插槽的数据 .子组件文件插槽上带的数据 
    在父组件的子组件标签里 让一个标签 带有slot-scope="xxx" 去接收 以便在下面进行调用
    
    父组件把数据给子组件,父=>子
    子组件把数据给插槽,并暴露给父组件接口
    父组件调用子组件的插槽slot接口和数据

20.用vnode来描述一个DOM结构

    在vue.js中存在一个VNode类,使用它可以实例化不同类型的vnode实例,
    而不同类型的vnode实例各自表示不同类型的DOM元素。
    例如,DOM元素有元素节点,文本节点,注释节点等,vnode实例也会对应着有元素节点和文本节点和注释节点。
    VNode类代码如下:
export default class VNode {
    constructor(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
        this.tag = tag
        this.data = data
        this.children = children
        this.text = text
        this.elm = elm
        this.ns = undefined
        this.context = context
        this.functionalContext = undefined
        this.functionalOptions = undefined
        this.functionalScopeId = undefined
        this.key = data && data.key
        this.componentOptions = componentOptions
        this.componentInstance = undefined
        this.parent = undefined
        this.raw = false
        this.isStatic = false
        this.isRootInsert = true
        this.isComment = false
        this.isCloned = false
        this.isOnce = false
        this.asyncFactory = asyncFactory
        this.asyncMeta = undefined
        this.isAsyncPlaceholder = false
    }
    get child() {
        return this.componentInstance
    }
}
    tag 属性即这个vnode的标签属性
    data 属性包含了最后渲染成真实dom节点后,节点上的class,attribute,style以及绑定的事件
    children 属性是vnode的子节点
    text 属性是文本属性
    elm 属性为这个vnode对应的真实dom节点
    key 属性是vnode的标记,在diff过程中可以提高diff的效率

21.diff算法的时间复杂度

    diff 算法用来比较两棵 Virtual DOM 树的差异,
    如果需要两棵树的完全比较,那么 diff 算法的时间复杂度为O(n^3)。
    但是在前端当中,你很少会跨越层级地移动 DOM 元素,
    所以 Virtual DOM 只会对同一个层级的元素进行对比,
    如下图所示, div 只会和同一层级的 div 对比,第二层级的只会跟第二层级对比,
    这样算法复杂度就可以达到 O(n)。

22.简述Vue中diff算法原理

    我们都知道真实dom的开销是很大的,这个跟性能优化中的重绘意义类似。
    某些时候我们修改了页面中的某个数据,如果直接渲染到真实DOM中会引起整棵树的重绘,
    那么我们能不能只让我们改变过的数据映射到真实 DOM,做一个最少的重绘呢,这就是diff算法要解决的事情
    对操作前后的dom树同一层的节点进行对比,一层一层对比,然后再插入真实的dom中,重新渲染

23.v-for中为什么要用key

    :key="唯一标识" 唯一标识可以是item里面id index等,
    因为vue组件高度复用增加Key可以标识组件的唯一性,
    key的作用主要是为了高效的更新虚拟DOM

24.描述组件渲染和更新过程

    把模板编译为render函数
    实例进行挂载, 根据根节点render函数的调用,递归的生成虚拟dom
    对比虚拟dom,渲染到真实dom
    组件内部data发生变化,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3

25.Vue中模板编译原理

    1). 目的
   实现初始化显示
    2). 整体流程
        1. 将el的所有子节点取出, 添加到一个新建的文档fragment对象中
         2. 对fragment中的所有层次子节点递归进行编译解析处理
        3. 将解析后的fragment添加到el中显示
    3). 编译/解析包含大括号表达式的文本节点: textNode.textContent = value
    4). 编译事件指令: elementNode.addEventListener('eventName', callback)
    5). 编译一般指令: elementNode.xxx = value

26.Vue中常见性能优化

    v-if 和 v-show 区分使用场景
    computed 和 watch 区分使用场景
    v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
    长列表性能优化
    事件的销毁
    图片资源懒加载
    路由懒加载
    第三方插件的按需引入

27.Vue中相同逻辑如何抽离

    使用mixin,mixin文件是一个对象,可以包含vue组件的任意成分。
    是分发Vue组件可复用功能的非常灵活的方式,
    当mixin被组件使用时,所有minxin里的属性/方法会与组件里的属性/方法混合
    
    在Vue组件中可以有mixins属性,该属性值类型为数组。将mixin引入,
    作为mixins数组的元素mixins: [mixin]
    组件A应用了mixin,两者的属性如methods,components和directives,将被混合为同一个对象,
    如果methods,components和directives中有同名的属性,则mixin中的将会被忽略。
    同名钩子函数会组成数组并都会被调用,并且mixin的钩子函数会比组件的钩子函数先被调用。

28.为什么要使用异步组件

    在一个 vue 单页面应用中,随着应用程序越来越大,功能越来越多,
    继而造成首页打开过慢的现象。在这种情况下,可以使用 vue 的异步组件与路由懒加载来进行首页打开速度优化
    
    异步组件,是指只有页面需要用到时才从服务器加载的组件。

29.谈谈你对keep-alive的了解

    keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染
    一般结合路由和动态组件一起使用,用于缓存组件
    当组件在 <keep-alive> 内被切换,
    它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
    如果一个tab组件,当你点击第一个组件的某个操作时,在切换到第二个组件去,然后在切回第一个组件,
    这个时候如果没有使用keep-alive,那它就会回到组件初始状态,
    如果使用,那他就会把第一个组件进行缓存,保留之前的状态

30.实现hash路由和history路由

    在项目中,我们用histoty路由经常会遇到这样一个问题,刷新某个路由路径时,会出现404的错误,
    它产生的原因就是项目根路径的path路径会被当做后台路由路径
    它的解决办法有两种,一种是当后台404的时候,让后台去处理返回首页,然后我们就可以当做前台路由来处理,
    另一种是改变history路由为hash路由,这样请求的路径都是#号之前的根路径,
    剩下的路由全部由我们前端来处理
    
    hash —— 即地址栏 URL 中的 # 符号
    比如这个 URL:http://www.123.com/#/hello,hash 的值为 #/hello。
    它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,
    因此改变 hash 不会重新加载页面。
    
    history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
    
    这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,
    它们提供了对历史记录进行修改的功能。
    只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求
    hash 模式和 history 模式都属于浏览器自身的特性,
    Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由
    
    前端路由中我们主要是利用history.replaceState() history.pushState或者改变hash值不会使页面重新加载,
    然后监听路由的变化来做到触发对应时间加载对应资源来重新绘制页面。
    这里比较重要的点是,监听hash模式用的是hashchange,history模式用的是popstate

31.Vue-Route中导航守卫有哪些

    全局守卫
        router.beforeEach 全局前置守卫 进入路由之前
        router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用
        router.afterEach 全局后置钩子 进入路由之后
    路由组件内的守卫
        beforeRouteEnter 进入路由前
        beforeRouteUpdate (2.2) 路由复用同一个组件时
        beforeRouteLeave 离开当前路由时

32.action和mutation的区别

    mutation:专注于修改State,理论上是修改State的唯一途径,必须同步执行
    action:业务代码、异步请求,可以异步,但不能直接操作State

33.简述vuex工作原理

34.Vue3.0你知道有哪些改进

    监测机制的改变
    3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。
    这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:
        只能监测属性,不能监测对象
        检测属性的添加和删除;
        检测数组索引和长度的变更;
        支持 Map、Set、WeakMap 和 WeakSet。
        
    压缩包体积更小
        当前最小化并被压缩的 Vue 运行时大小约为 20kB(2.6.10 版为 22.8kB)。
        Vue 3.0捆绑包的大小大约会减少一半,即只有10kB!
        
    Virtual DOM 重构