美文网首页
2021最新Vue 面试题

2021最新Vue 面试题

作者: play_0 | 来源:发表于2021-05-11 11:19 被阅读0次

    Vue面试题

    1. v-html 会有XSS风险,会覆盖子组件

    2. computed 有缓存,data不变则不会重新计算

    3. watch 默认不会深度监听,要deep:true开启

    4. watch 监听引用类型,拿不到oldVal,因为指针相同,此时已经指向了新的val

    5. style指令需要驼峰式命名方式写,例如{fontSize: '10px'}

    6. v-if v-else 可使用变量,也可使用===表达式

    7. v-if 和 v-show 的区别

      v-if false不渲染,适合更新不频繁的,节省性能

      v-show false也渲染

    8. v-for 和 v-if 不能一起使用,v-for 比 v-if 优先级高,影响性能

    9. 事件的event是原生的event,没有装饰过,事件是挂载到当前元素

    10. v-for 的 key 因为 VDOM 的 diff 算法,通过判断新vnode和旧vnode的key是否相等,从而复用与新节点对应的老节点,节约性能开销

    11. 用index做key在diff算法中不起作用,用index拼接其他值会导致节点不能复用,所以用唯一值做key,比如数据id

    12. props 和 $emit

      props父组件通过子组件属性给子组件传值

      $emit子组件绑定父组件方法,在子组件内使用$emit触发父组件的方法

    13. Vue组件如何通讯

      父子组件事件props $emit

      自定义事件 $emit $on

      通过vuex

    14. 组件间通讯-自定义事件(常用兄弟组件通讯)

      Vue自有实现,使用EventBus方式(bus总线机制/发布订阅者模式/观察者模式)

      import Vue from 'vue'
      var event = new Vue()
      export default event
      

      在A组件内使用

      event.$emit('onHandle', params)
      

      在B组件内的mounted中绑定自定义事件

      event.$on('onHandle', this.handle)
      

      在B组局内beforeDestroy中及时销毁监听事件,否则可能造成内存泄露

      event.$off('onHandle', this.handle)
      
    15. 组件生命周期 <font color="red">* 必须背会</font>

      挂载

      beforeCreate->create->beforeMount->mounted
      

      beforeCreate里没有this,实例还未初始化,在数据观测(data observer)和event/watcher事件配置前

      create 初始化示例,没有渲染,取不到dom,没有$el,通常初始化某些属性值

      beforeMount 挂载开始之前,相关渲染首次被调用

      mounted 是渲染完了,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作

      先父组件create->子组件create->子组件mounted->父组件mounted


      更新

      beforeUpdate->updated
      

      beforeUpdate 重新渲染和打补丁之前,在这里更改状态不会触发重新渲染

      updated 由于数据更改导致的重新渲染和打补丁触发

      先父组件beforeUpdate-->子组件beforeUpdate->updated->父组件->updated


      销毁

      beforeDestroy->Destroyed
      

      beforeDestroy 实例销毁之前,里面可以用this

      Destroyed 实例销毁之后,所以东西解绑,移除所以监听,销毁所以子实例,在服务端渲染不会被调用

      先父组件beforeDestroy-->子组件beforeDestroy->Destroyed->父组件->Destroyed

    16. 高级特性

      • <font color="red">可以不深入,但必须知道</font>
      • <font color="red">熟练基本用法,了解使用场景</font>
      • <font color="red">最好能和自己的项目经验结合</font>
      1. 自定义v-model

        v-model会默认利用名为value的prop和名为input的事件,model会避免单选,复选将value特性用于不同目的的冲突

        v-model等于

        <input type="text" 
              :value="val" 
              @input="val=$event.target.value">
        

        demo:

        //父组件
        <CustomVModel v-model="name"/>
        <script>
        import CustomVModel from './CustomVModel'
        export default {
            components: {
                CustomVModel
            },
            data() {
                return {
                    name: ''
                }
            }
        }
        </script>
        // 子组件内
        <template>
           <!--
           1. input使用了 :value 而不是 v-model
           2. change 和 model.even 要对应
           3. text 属性要对应 model.prop 和 props.text
           -->
           <input type="text" 
                   :value="text" 
                   @input="$emit('change', $event.target.value)"
            />
        </template>
        <script>
        export default {
            // 改变默认v-model的绑定属性和抛出事件
            model: {
                prop: 'text' // 对应props text,
                event: 'change'
            },
            props: {
                text: String,
                default() {
                    return ''
                }
            }
        }
        </script>
        
      2. $nextTick、$refs

        $nextTick

        Vue是异步渲染,$nextTick会在DOM渲染之后被触发

        页面渲染会将 data 的修改做整合,多次 data 修改只会渲染一次


        $refs 在节点内写ref="dom" 使用this.$refs.dom拿到节点的dom元素

      3. slot

        基本使用

        父组件在子组件内插入节点内容

        // 父组件
        <SlotDemo>
           {{user.name}}
        </SlotDemo>
        // 子组件 SlotDemo
        <div>
            <slot>默认显示</slot>
        </div>
        

        作用域插槽

        父组件在子组件内插入节点内容,使用子组件的数据

        // 父组件
        <ScopedSlotDemo>
           <template v-slot="slotProps">
               {{slotProps.slotData.name}}
            </template>
        </ScopedSlotDemo>
        // 子组件 ScopedSlotDemo
        <div>
            <slot :slotData="user"></slot>
        </div>
        

        具名插槽

        用于定义多个插槽

        // 父组件
        <NameSlot>
            <template v-slot:header>
               <h1>插入 header slot中</h1>
            </template>
           <p>插入 main slot中,即未命名的 slot</p>
           <template v-slot:footer>
               <p>插入 footer slot中</p>
            </template>
        </NameSlot>
        // 子组件 NameSlot
        <div class="container">
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        </div>
        
      4. 动态、异步组件

        动态组件

        用法 :is='component-name'

        需要根据数据,动态渲染的场景,即组局类型不确定,例如 信息流

        <div v-for="item in components" v-key="id">
           <component :is="item.name" />    
        </div>
        
        <script>
        import TextInfoComponent from './TextInfoComponent'
        import ImageInfoComponent from './TextInfoComponent'
        export default {
            components: {
                TextInfoComponent,
                ImageInfoComponent
            },
            data() {
                return {
                   components: [{
                       id: 1,
                       name: 'TextInfoComponent',
                   },{
                       id: 2,
                       name: 'ImageInfoComponent',
                   }]
                }
            }
        }
        </script>
        

        异步组件

        import() 函数

        按需加载, 异步加载大组件

        export default {
            conponents: {
                component: () => import('./component')
            }
        }
        
      5. keep-alive

        缓存组件,频繁切换,不需要重复渲染 例如 tab切换

        <keep-alive>
         <KeepAliveStage></KeepAliveStage>
        </keep-alive>
        <script>
        import KeepAliveStage from './KeepAliveStage'
        export default {
            conponents: {
                KeepAliveStage
            }
        }
        </script>
        
      6. mixin

        多组件有相同的逻辑,抽离出来,和主组件混合合并代码

        会出现问题

        • 变量来源不明确,不利于阅读
        • 多mixin会造成命名冲突
        • mixin和组件可能出现多对多的关系,复杂度较高

        Vue 3 提出的Composition API 旨在解决这些问题

        // 定义 ./myMixin.js
        export default {
            data() {
                return {}
            },
            mounted() {},
            methots: {}
        }
        
        // 使用
        import myMixin from './myMixin'
        export default {
            mixins: [myMixin] // 可以多个,自动合并
        }
        
    17. Vuex 使用

      可能会考察 state 的数据结构设计

      vuex.png
    + 基本概念
    
    1. state // 单一状态树
    2. getters // 获取属性,计算属性啥的
    3. actions // 类似控制器,commit 多个 mutations,这里可以异步操作,其他的不行
    4. mutations // 更改状态,必须是同步函数
    5. module // 模块化Store
    6. plugins //插件,相当mutation的拦截器,提供store.subscribe((mutation, state) => {})
    
    --------------
    
    + Vue组件内
    
    1. dispatch // 用来触发 actions
    2. commit // 用来触发 mutations
    3. mapState // 映射 state 的函数,computed 中 ...mapState()
    4. mapGetters  // 映射 getters 的函数,computed 中 ...mapState()
    5. mapActions // 映射 actions 的函数,methods 中 ...mapActions()
    6. mapMutations // 映射 mutations 的函数,methods 中 ...mapMutations()
    
    1. Vue-router 使用

      路由模式(hash,H5 history),后者需要server支持

      mode:‘history’

      路由配置(动态路由,懒加载)

      :id那种,在$route.params.id取到

    2. Vue 原理

      • 如何理解MVVM

        mvvm.png

        Model-View-ViewModel 数据模型-视图-视图数据

        数据驱动视图

        View和Model没有直接关联,ViewMode是给他们提供双向数据绑定的联系,优点是让开发者只需关注业务,不需要手动操作DOM,也不需要关注数据状态同步的问题

      • 监听data变化的核心API是什么,怎么深度监听-递归

        Vue 2 是通过 Object.defineProperty 的 getter 和 setter,并结合观察者模式实现数据绑定的

        能监听属性的get set方法

        缺点

        用于深度监听要递归到底,一次性计算量大

        无法监听新增属性/删除属性

        无法原生监听数组,需要特殊处理

        Vue 3 是通过 es6的 proxy

        优点是之前操作的是对象的属性,现在是操作对象,对js引擎比较友好

        缺点是兼容不好,不能用polyfill,不支持低版本浏览器

        监听数组-重新定义数组原型链

        const oldArrayProperty = Array.prototype
        // 创建新对象,原型指向oldArrayProperty,在扩展新的方法不会影响原型
        const arrProto = Object.create(oldArrayProperty)
        ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
            arrProto[methodName] = function() {
                updateView() // 更新视图
                oldArrayProperty[methodName].call(this, ...arguments)
            }
        })
        
      • 虚拟DOM

        vdom (Virtual DOM) 是实现vue的重要基石

        用JS模拟DOM结构,计算出最小的变更,操作DOM

        数据驱动视图,控制DOM操作

      • diff算法

        diff算法是vdom中的核心,最关键的部分

        vue2 参考snabbdom,双端比较

        diff01.png

        核心思路

        patch新旧vnode的children

        新的没有不管旧的有没有,就移除children

        新旧都有,就去对比新旧vnode的children

        新有旧没有,就添加children

        对比新旧vnode的children,先用四个指针指向新旧nodeChildren的头尾

        然后循环指针到中间,循环内进行对比key和sel是否相等

        1. 旧children的开始 和 新children的开始 对比
        2. 旧children的结束 和 新children的结束 对比
        3. 旧children的开始 和 新children的结束 对比
        4. 旧children的结束 和 新children的开始 对比
        5. 以上有相同的,就执行处理新旧节点的children,并对应移动指针
        6. 以上都没找到相同的,则去用新的key看看旧的里面有没有,如果没有则插入,然后移动指针,如果有再判断sel是否相同,不相同就插入,然后移动指针,相同就执行处理新旧节点的children,去掉旧节点,插入新节点,然后移动指针
        // children 和 text 是不会共存的,要么是节点要么是文本
        // elm 对应的dom元素
        // 所有元素都可以有key
        Vnode = {sel, data, children, text, elm, key}
        
        //先创建Vnode
        h() => Vnode
        
        patch(oldVnode, newVnode) => {
            if 第一个参数不是Vnode
                创建一个空的Vnode,关联到这个elm元素
            
            if 判断Vnode是否相同,则去对比(两个Vnode的key 和 sel 都相等)
                Vnode对比: patchVnode()
            else 不同的Vnode直接删掉重建 
        }
        
        patchVnode(oldVnode, newVnode) => {
            执行 prepatch hook
            设置 vnode.elm,把新的 elm 赋值成旧的 elm, 让新的知道更新到了哪个 elm
            得到 oldChildren 和 newChildren
            if 判断newVnode 有没有 children, newVnode.text == undefined (vnode.children 一般有值) 
                if 新旧都有 children
                    更新children: updateChildren()
                else if 新 children 有,旧 children 无 (旧 text 有)
                    if 如果就的 text 有值,则清空 text
                    添加 children
                else if 新 children 无,旧 children 有
                    移除 children
                else 旧 text 有
                    清空 text
                清空 text
            else vnode.children 没有值,则删除旧的 children 并设置清空 text
        }
        
        // 核心方法
        updateChildren(elm, oldChildren, newChildren) => {
            // 用于循环从四周到中间
            定义指针 oldStartIndex, oldEndIndex, newStartIndex, oldStartIndex
            
            while(指针到中间停止) {
                if 判断元素是否是空,操作指针移动
                else if oldStart == newStart 开始和开始,判断是否相同(key 和 sel 都相等)
                    patchVnode(), 操作指针移动
                else if oldEnd == newEnd 结束和结束,判断是否相同(key 和 sel 都相等)
                    patchVnode(), 操作指针移动
                else if oldStart == newEnd 开始和结束,判断是否相同(key 和 sel 都相等)
                    patchVnode(), 操作指针移动
                else if oldEnd == newStart 结束和开始,判断是否相同(key 和 sel 都相等)
                    patchVnode(), 操作指针移动
                else 以上四个都未命中
                    // 用新节点 key,判断能否对应 oldChildren 中某个节点的 key
                    在oldChildren中,用新节点的key去拿节点
                    if 没拿到没对应上,则
                        插入新节点, 操作指针移动
                    else 对应上
                        if sel 是否相等
                            插入新节点, 操作指针移动
                        else key 和 sel 都相等
                            patchVnode(),去掉旧节点,插入新节点, 操作指针移动
            }
        }
        

        vue3 参考inferno,最长递增子序列

        两个理念。第一个是相同的前置与后置元素的预处理;第二个则是最长递增子序列

        1. 从头对比找到相同的节点patch,发现不同则跳出

        2. 如果1没有patch完,则从后往前找相同的节点patch,发现不同则跳出

        3. 如果新节点大于与旧节点,对剩余的都创建新的vnode

        4. 如果旧节点大于新节点,对于超出的旧节点全部卸载

        5. 如果3,4都没有,则是对于不确定的元素(没有patch到相同的vnode)

          1. 把没有比较过的新vnode保存在map里

          2. 记录已经patch节点的熟练patched,没有经过patch的新节点数量tuBePatched

          3. 建立一个数组newIndexToOldIndexMap,每个元素都记录旧节点的索引,这个数组的索引就是新节点的索引

          4. 遍历旧节点

            1. 如果toBePatched为0,那么统一卸载旧节点
            2. 如果旧节点key存在,通过key找到对应新节点的index
            3. 如果旧节点key不存在,遍历剩下的所有新节点,试图找到新节点对应的index
            4. 如果没有找到对应的新节点就卸载旧节点
            5. 如果找到对应的新节点,把旧节点的索引记录在新节点的数组中,如果节点发生移动就记录已经移动了,最后patch新节点
          5. 如果发生移动,根据newIndexToOldIndexMap找到最长稳定序列,

          6. 如果证明不存在旧节点就创建新节点,对于发生移动的节点进行移动处理

    3. 模版编译

      模版编译为 render 函数,执行 render 函数返回 vnode

      基于 vnode 再执行 patch 和 diff

      使用webpack vue-loader

      • JS的 with 语法

        改变 {} 内自由变量的查找规则,当做 obj 属性来查找

        如果找不到匹配的 obj 属性,就会报错

        with 要慎用,它打破了作用域规则,易读性变差

        // with 能改变 {} 内自由变量的查找方式,将 {} 内自由变量,当做 obj 的属性来查找
        const obj = {a:100, b:200}
        console.log(obj.a)
        console.log(obj.b)
        console.log(obj.c) // undefined
        with(obj) {
            console.log(a)
            console.log(b)
            console.log(c) // 会报错
        }
        
    4. 组件 渲染/更新 过程

    data.png
    > 初次渲染的过程
    >
    > > 解析模版为 render 函数
    > >
    > > 触发响应式,监听 data 属性的 getter setter
    > >
    > > 执行 render 函数,生成 vnode, 执行 patch(elm, vnode)
    >
    > 更新过程
    >
    > > 修改 data,触发 setter
    > >
    > > 重新执行 render 函数,生成 newVnode
    > >
    > > 执行 patch(vnode, newVnode)
    >
    > 异步渲染 $nextTick
    >
    > > 汇总 data 的修改,一次性更新视图
    > >
    > > 减少 DOM 操作次数,提升性能
    
    1. 路由原理

      稍微复制的SPA,都需要路由

      hash-window.onhashchange

      H5 history-history.pushState 和 window.onpopstate

      H5 history 需要后端支持

      两者选择

      toB 推荐hash,简单易用,对 url 规范不敏感

      toC 可以考虑选择H5 history,但需要服务端支持

      能选择简单的就别用复杂的,要考虑成本和收益

      hash 特点

      hash 变化会触发网页跳转,即浏览器的前期、后退

      hash 变化不会刷新页面,SPA必需的特点

      hash 永远不会提交到server端(前端自生自灭)

      H5 history

      用 url 规范的路由,但跳转时不刷新页面

      history.pushState

      window.onpopstate

    2. 面试题

      v-show 和 v-if 的区别

      v-show 通过 css display 控制显示和隐藏

      v-if 是组件真正的渲染和销毁,而不是显示和隐藏

      频繁切换显示状态用 v-show,否则用 v-if

      为什么 v-for 中用 key

      必须用 key,而且不能是 index 和 random

      diff 算法中通过 tag 和 key 来判断,是否是 sameNode

      减少渲染次数,提升渲染性能

      描述 vue 组件的生命周期

      单组件生命周期图

      父子组件生命周期关系比如 mount 和 update

      Vue 组件如何通讯

      父子组件 props 和 this.$emit

      自定义事件 event.on event.off event.$emit

      vuex

      描述组件渲染和更新的过程

      渲染图

      双向数据绑定 v-model 的实现原理

      input 元素的 value = this.name

      绑定 input 事件 this.name = $event.target.value

      data 更新触发 re-render

      对 MVVM 的理解

      computed 有何特点

      缓存,data 不变不会重新计算

      提高性能

      为何组件 data 必须是一个函数

      js 特性导致的,Object 是引用数据类型,会导致同一个属性改一个都变了,是函数的时候才能实例独立不相互影响

      ajax 应该放在哪个生命周期

      mounted

      js 是单线程,ajax 是异步获取数据

      放在 mounted 之前没有用,只会让逻辑更加混乱,除非有特殊需求

      如何将组件所有的 props 传递给子组件

      $props

      <Uer v-bind="$props" />

      如何自己实现 v-model

      多个组件有相同的逻辑,如何抽离

      mixin

      mixin 的缺点

      何时要使用异步组件

      加载大组件

      路由异步加载

      何时需要使用 keep-alive

      缓存组件,不需要重复渲染

      多个静态 tab 页的切换

      优化性能

      何时需要使用beforeDestory

      解绑自定义事件 event.$off

      清楚定时器

      解绑自定义的 DOM 事件,如 window scroll 等

      什么是作用域插槽

      把子组件的数据传给父组件显示

      vuex 中 action 和 mutation 有何区别

      action 中处理异步,mutation 不可以

      mutation 做原子操作

      action 可以整合多个 mutation

      vue-router 常用的路由模式

      hash 默认

      H5 history

      vue-router 两种模式是怎么实现的

      hash window.onhashchange

      H5 history window.history.pushState window.history.onpopstate

      vue-router 两种模式的区别

      hash有#号,能控制浏览器前后跳转,不会提交到server端

      H5 history 是规范 url,需要 server 端配合

      如何配置 vue-router 异步加载

      通过 () => import()

      用 vnode 描述一个 DOM 结构

      监听 data 变化的核心 API 是什么

      Object.defineProperty

      有何缺点

      深度监听需要递归、监听数组需要特殊处理,无法监听新增和删除的属性,使用vue.set vue.delete

      Vue3 Proxy 兼容问题,而且不能 polyfill

      Vue 如何监听数组变化

      Object.defineProperty 不能监听数组变化

      重新定义原型,重新push pop等方法,实现监听

      Proxy 可以原生支持监听数组变化

      描述响应式原理

      监听 data 变化

      组件渲染和更新的流程

      diff 算法的时间复杂度

      O(n) ,在O(n^3)基础上做的调整,只比较同一层级,比较tag不相同直接销毁重建,通过tag和key判断是不是同一组件,是就不重复对比

      简述 diff 算法过程

      patch(elem, vnode) 和 patch(vnode, newVnode)

      patchVnode 和 addVnodes 和 removeVnodes

      updateChildren,通过key判断是不是同一节点

      vue为何是异步渲染,$nextTick 何用

      异步渲染,合并 data 修改,提高性能

      $nextTick 在 DOM 更新之后,触发回调

      vue 常见性能优化

      合理使用 v-show v-if

      合理使用 computed

      v-for 时加 key,以及避免和 v-if 同时使用

      自定义事件,DOM事件及时销毁

      合理使用异步组件

      合理使用 keep-alive

      data 层级不要太深

      使用 vue-loader 在开发环境做模版编译(预编译)

      webpack 层面的优化

      前端通用的性能优化,如图片懒加载

      使用ssr

    3. Vue 3

      Vue3 比 Vue2 有什么优势

      性能更好

      体积更小

      更好的 ts 支持

      更好的代码组织

      更好的逻辑抽离

      更多新功能

      Vue3 声明周期

      Options API: beforeDestory 改为 beforeUnmount destoryed 改为 unmouted

      升级内容

      全部用 ts 重写

      性能提升,代码量减少

      调整部分API

    4. 持续更新中...

    相关文章

      网友评论

          本文标题:2021最新Vue 面试题

          本文链接:https://www.haomeiwen.com/subject/kqugdltx.html