Vue中级面试题汇总

16,364 阅读10分钟

Vue在created和mounted这两个生命周期中请求数据有什么区别呢?

参考答案 在created中,页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态,DOM节点没出来,无法操作DOM节点。在mounted不会这样,比较好。

v-model的原理是什么?

参考答案
<template>
    <div>
        <my-component v-model="value"></my-component>
        <!-- 等同 -->
        <my-component :value="value" @input="value=$event"></my-component>
        <button @click="value=true">显示</button>
    </div>
</template>
<script>
    export default{
        data(){
            return{
                value:false,
            }
        },
        components:{
            myComponent:resolve =>require(['./my_component'],resolve),
        }
    }
</script>
<template>
    <div v-show="value">
        <span>我的组件</span>
        <button @click="$emit('input',false)">隐藏</button>
    </div>
</template>
<script>
    export default{
        props:{
            value:{
                type:Boolean,
                default:false,
            }
        },
        data(){
            return{}
        },
    }
</script>

说说你对keep-alive的理解

参考答案 keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

其有三个参数

  • include定义缓存白名单,会缓存的组件;
  • exclude定义缓存黑名单,不会缓存的组件;
  • 以上两个参数可以是逗号分隔字符串、正则表达式或一个数组,include="a,b":include="/a|b/":include="['a', 'b']"
  • 匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配;
  • max最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉;
  • 不会在函数式组件中正常工作,因为它们没有缓存实例;
  • 当组件在内被切换,它的activated和deactivated这两个生命周期钩子函数将会被对应执行。

v-if和v-for的优先级是什么?如果这两个同时出现时,那应该怎么优化才能得到更好的性能?

参考答案

当它们处于同一节点,v-for的优先级比v-if更高,这意味着v-if将分别重复运行于每个v-for循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用。

<ul>
    <li v-for="item in items" v-if="item.show">{{item}}</li>
</ul>

如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 <template>)上。

<ul v-if="items.length">
    <li v-for="item in items">{{item}}</li>
</ul>

使用v-for遍历对象时,是按什么顺序遍历的?如何保证顺序?

参考答案 按Object.keys()的顺序的遍历,转成数组保证顺序。

在v-for中使用key,会提升性能吗,为什么?

参考答案

主要看v-for渲染的是什么。

  • 如果渲染是一个简单的列表,如不依赖子组件状态或临时DOM状态(例如:表单输入值)的列表渲染输出,不用key性能会更好,因为不用key采用的是“就地更新”的策略。如果数据项的顺序被改变, Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素。
    <template>
        <div>
            <span v-for="item in lists">{{item}}</span>
        </div>
    </template>
    <script>
    export default {
        data() {
            return {
                lists: [1, 2, 3, 4, 5]
            }
        },
    }
    </script>
    
    以上的例子,v-for的内容会生成以下的DOM节点数组,我们给每一个节点标记一个身份id,以辨别节点的位置:
    [
        '<span>1</span>', // id: A
        '<span>2</span>', // id:  B
        '<span>3</span>', // id:  C
        '<span>4</span>', // id:  D
        '<span>5</span>'  // id:  E
    ]
    
    将lists中的数据进行位置调换,变成[2,4,3,1,5],在没有key的情景下,节点位置不变,但是节点的内容更新了,这就是“就地更新”
    [
        '<span>2</span>', // id: A
        '<span>4</span>', // id:  B
        '<span>3</span>', // id:  C
        '<span>1</span>', // id:  D
        '<span>5</span>'  // id:  E
    ]
    
    但是在有key的情景下,节点位置进行了交换,但是内容没有更新
    [
        '<span>2</span>', // id: B
        '<span>4</span>', // id:  D
        '<span>3</span>', // id:  C
        '<span>1</span>', // id:  A
        '<span>5</span>'  // id:  E
    ]
    
  • 如果渲染不是一个简单的列表,用key性能会更好一点,因为vue是采用diff算法来对比新旧虚拟节点来更新节点,在diff算法中,当新节点跟旧节点头尾交叉对比没有结果时,先处理旧节点生成一个健为key,值为节点下标index的map映射,如果新节点有key,会通过map映射找到对应的旧节点,如果新节点没有key,会采用遍历查找的方式去找到对应的旧节点,一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。
    // vue源码 src/core/vdom/patch.js 488行
    // 以下是为了阅读性进行格式化后的代码
    // oldCh 是一个旧虚拟节点数组
    // oldKeyToIdx map映射对象
    // idxInOld 对比后得到旧节点下标
    if (isUndef(oldKeyToIdx)) {
        oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
    }
    if (isDef(newStartVnode.key)) {
        // map 方式获取
        idxInOld = oldKeyToIdx[newStartVnode.key]
    } else {
        // 遍历方式获取
        idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
    }
    
    创建map函数
    function createKeyToOldIdx(children, beginIdx, endIdx) {
        let i, key
        const map = {}
        for (i = beginIdx; i <= endIdx; ++i) {
            key = children[i].key
            if (isDef(key)) map[key] = i
        }
        return map
    }
    
    遍历寻找函数
    // sameVnode 是对比新旧节点是否相同的函数
    function findIdxInOld(node, oldCh, start, end) {
        for (let i = start; i < end; i++) {
            const c = oldCh[i];
            if (isDef(c) && sameVnode(node, c)) return i
        }
    }
    

key除了在v-for中使用,还有什么作用?

参考答案

还可以强制替换元素/组件而不是重复使用它。在以下场景可以使用

  • 完整地触发组件的生命周期钩子
  • 触发过渡
<transition>
  <span :key="text">{{ text }}</span>
</transition>

当 text 发生改变时,<span>会随时被更新,因此会触发过渡。

使用key要什么要注意的吗?

参考答案
  • 不要使用对象或数组之类的非基本类型值作为key,请用字符串或数值类型的值;

  • 不要使用数组的index作为key值,因为在删除数组某一项,index也会随之变化,导致key变化,渲染会出错。

    例:在渲染[a,b,c]用 index 作为 key,那么在删除第二项的时候,index 就会从 0 1 2 变成 0 1(而不是 0 2),随之第三项的key变成1了,就会误把第三项删除了。

说说组件的命名规范

参考答案

给组件命名有两种方式,一种是使用链式命名my-component,一种是使用大驼峰命名MyComponent,

  • 在字符串模板中<my-component></my-component><MyComponent></MyComponent>都可以使用,

  • 在非字符串模板中最好使用<MyComponent></MyComponent>,因为要遵循W3C规范中的自定义组件名 (字母全小写且必须包含一个连字符),避免和当前以及未来的 HTML 元素相冲突。

为什么组件中data必须用函数返回一个对象?

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

Vue父子组件双向绑定的方法有哪些?

参考答案
  • 通过在父组件上自定义一个监听事件<myComponent @diy="handleDiy"></myComponent>,在子组件用this.$emit('diy',data)来触发这个diy事件,其中data为子组件向父组件通信的数据,在父组件中监听diy个事件时,可以通过$event访问data这个值。
  • 通过在父组件上用修饰符.sync绑定一个数据<myComponent :show.sync="show"></myComponent>,在子组件用this.$emit('update:show',data)来改变父组件中show的值。
  • 通过v-model

组件的name选项有什么作用?

参考答案
  • 递归组件时,组件调用自身使用;
  • is特殊特性和component内置组件标签时使用;
  • keep-alive内置组件标签中include exclude属性中使用。

什么是递归组件?举个例子说明下?

参考答案

递归引用可以理解为组件调用自身,在开发多级菜单组件时就会用到,调用前要先设置组件的name选项, 注意一定要配合v-if使用,避免形成死循环,用element-vue组件库中NavMenu导航菜单组件开发多级菜单为例:

<template>
    <el-submenu :index="menu.id" popper-class="layout-sider-submenu" :key="menu.id">
        <template slot="title">
            <Icon :type="menu.icon" v-if="menu.icon"/>
            <span>{{menu.title}}</span>
        </template>
        <template v-for="(child,i) in menu.menus">
            <side-menu-item v-if="Array.isArray(child.menus) && child.menus.length" :menu="child"></side-menu-item>
            <el-menu-item :index="child.id" :key="child.id" v-else>
                <Icon :type="child.icon" v-if="child.icon"/>
                <span>{{child.title}}</span>
            </el-menu-item>
        </template>
    </el-submenu>
</template>
<script>
    export default{
        name: 'sideMenuItem',
        props: {
            menu: {
                type: Object,
                default(){
                    return {};
                }
            }
        }
    }
</script>

说说你对slot的理解?slot使用场景有哪些?

参考答案

组件的插槽功能

说下$attrs$listeners的使用场景?

参考答案
  • $attrs: 包含了父作用域中(组件标签)不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。 在创建基础组件时候经常使用,可以和组件选项inheritAttrs:false和配合使用在组件内部标签上用v-bind="$attrs"将非prop特性绑定上去;
  • $listeners: 包含了父作用域中(组件标签)的 (不含.native) v-on 事件监听器。 在组件上监听一些特定的事件,比如focus事件时,如果组件的根元素不是表单元素的,则监听不到,那么可以用v-on="$listeners"绑定到表单元素标签上解决。

说说你对provide和inject的理解

参考答案

组件的依赖注入

EventBus注册在全局上时,路由切换时会重复触发事件,如何解决呢?

参考答案

在有使用$on的组件中要在beforeDestroy钩子函数中用$off销毁。

Vue组件里写的原生addEventListeners监听事件,要手动去销毁吗?为什么?

参考答案

要,不然会造成多次绑定和内存泄露。关于移除事件监听的坑

Vue组件里的定时器要怎么销毁?

参考答案
  • 如果页面上有很多定时器,可以在data选项中创建一个对象timer,给每个定时器取个名字一一映射在对象timer中, 在beforeDestroy构造函数中for(let k in this.timer){clearInterval(k)}
  • 如果页面只有单个定时器,可以这么做。
    const timer = setInterval(() =>{}, 500);
    this.$once('hook:beforeDestroy', () => {
       clearInterval(timer);
    })
    

Vue中能监听到数组变化的方法有哪些?为什么这些方法能监听到呢?

参考答案
  • push()pop()shift()unshift()splice()sort()reverse(),这些方法在Vue中被重新定义了,故可以监听到数组变化;
  • filter()concat()slice(),这些方法会返回一个新数组,也可以监听到数组的变化。

在Vue中那些数组变化无法监听,为什么,怎么解决?

参考答案
  • 利用索引直接设置一个数组项时;

  • 修改数组的长度时。

    • 第一个情况,利用已有索引直接设置一个数组项时Object.defineProperty()可以监听到,利用不存在的索引直接设置一个数组项时Object.defineProperty()不可以监听到,但是官方给出的解释是由于JavaScript的限制,Vue不能检测以上数组的变动,其实根本原因是性能问题,性能代价和获得的用户体验收益不成正比。
    • 第二个情况,原因是Object.defineProperty()不能监听到数组的length属性。
  • this.$set(this.items, indexOfItem, newValue)this.items.splice(indexOfItem, 1, newValue)来解决第一种情况;

  • this.items.splice(newLength)来解决第二种情况。

在Vue中那些对象变化无法监听,为什么,怎么解决?

参考答案
  • 对象属性的添加
  • 对象属性的删除

因为Vue是通过Object.defineProperty来将对象的key转成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性,所以才会导致上面对象变化无法监听。

  • this.$set(this.obj,"key","newValue")来解决第一种情况;
  • Object.assign来解决第二种情况。

删除对象用delete和Vue.delete有什么区别?

参考答案
  • delete:只是被删除对象成员变为' 'undefined,其他元素键值不变;
  • Vue.delete:直接删了对象成员,如果对象是响应式的,确保删除能触发更新视图,这个方法主要用于避开 Vue 不能检测到属性被删除的限制。

watch和计算属性有什么区别?

参考答案
  • watch:一个数据影响多个数据,当需要在数据变化时执行异步或开销较大的操作时;
  • 计算属性:一个数据受多个数据影响。是基于它的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它才会重新求值。

计算属性和方法有什么区别?

参考答案
  • 计算属性:是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。
  • 方法:每当触发重新渲染时,调用方法将总会再次执行函数。当我们不希望有缓存,可以使用方法,但是如果求值开销大时建议用计算属性。

过渡动画实现的方式有哪些?

参考答案

你有写过自定义指令吗?自定义指令的生命周期(钩子函数)有哪些?

参考答案

自定义指令的钩子函数

手写一个自定义指令及写出如何调用

参考答案

注册一个让字体颜色闪烁的指令v-color

Vue怎么定义全局方法

参考答案

有三种

  • 挂载在Vue的prototype上

    // base.js
    const install = function (Vue, opts) {
        Vue.prototype.demo = function () {
            console.log('我已经在Vue原型链上')
        }
    }
    export default {
        install
    }
    
    //main.js
    //注册全局函数
    import base from 'service/base';
    Vue.use(base);
    
  • 利用全局混入mixin

  • this.$root.$on绑定方法,用this.$root.$off解绑方法,用this.$root.$emit全局调用。

    this.$root.$on('demo',function(){
        console.log('test');
    })
    this.$root.$emit('demo');
    this.$root.$off('demo');
    

说说你对DOM选项el、template、render的理解?

参考答案
  • el:提供一个在页面上已存在的DOM元素作为Vue实例的挂载目标。可以是CSS选择器,也可以是一个HTMLElement实例。
    • 因为所有的挂载元素会被Vue生成的DOM替换。因此不推荐挂载Vue实例到html或者body上。
    • 如果在const vm = new Vue({})中存在这个选项,实例将立即进入编译过程,否则,需要显式调用vm.$mount()手动开启编译。
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
    </head>
    <body>
        <div id="app">我是el挂载的内容:小明今年{{age}}岁了</div>
    </body>
    <script>
        const vm= new Vue({
            el:'#app',
            data:{
                age:17
            },
        }
    </script>
</html>
<script>
    const vm= new Vue({
        data:{
            age:17
        },
    })
    vm.$mount('#app')
</script>

  • template:一个字符串模板作为Vue实例的标识使用。如果el存在,模板将会替换挂载的元素。挂载元素的内容都将被忽略,除非模板的内容有分发插槽。
    • 如果值以 # 开始,则它将被用作选择符,并使用匹配元素的 innerHTML 作为模板。
<script>
    const vm= new Vue({
        el:'#app',
        data:{
            age:17
        },
        template:'<div>我是template的内容:小明今年{{age}}岁了</div>',
    })
</script>
<script type="x-template" id="mb">
	<div>我是template的内容:小明今年{{age}}岁了</div>
</script>
<script>
    const vm= new Vue({
        el:'#app',
        data:{
            age:17
        },
        template:'#mb',
    })
</script>
<body>
    <div id="app">
        我是el挂载的内容:小明今年{{age}}岁了
    </div>
    <template id="mb">
        <div>我是template的内容:小明今年{{age}}岁了</div>
    </template>
</body>
<script>
    const vm= new Vue({
        el:'#app',
        data:{
            age:17
        },
        template:'#mb',
    })
</script>

  • render :Vue 选项中的 render 函数若存在,则 Vue 构造函数不会从 template 选项或通过 el 选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
    </head>
    <body>
        <div id="app">
            我是el挂载的内容:小明今年{{age}}岁了
        </div>
    </body>
    <script>
        const vm= new Vue({
            el:'#app',
            data:{
                age:17
            },
            template:'<div>我是template的内容:小明今年{{age}}岁了</div>',
            render(h){
                return h('div',`我是render的内容:小明今年${this.age}岁了`)
            }
        })
    </script>
</html>

<template></template>有什么用?

参考答案 当做一个不可见的包裹元素,减少不必要的DOM元素,整个结构会更加清晰。

Vue怎么改变插入模板的分隔符?

参考答案

delimiters选项,其默认是["{{", "}}"]

// 将分隔符变成ES6模板字符串的风格
new Vue({
  delimiters: ['${', '}']
})

Vue变量名如果以_、$开头的属性会发生什么问题?怎么访问到它们的值?

参考答案

_ $ 开头的属性 不会 被 Vue 实例代理,因为它们可能和 Vue 内置的属性、API 方法冲突,你可以使用例如 vm.$data._property 的方式访问这些属性。

怎么捕获Vue组件的错误信息?

参考答案
  • errorCaptured是组件内部钩子,当捕获一个来自子孙组件的错误时被调用,接收errorvminfo三个参数,return false后可以阻止错误继续向上抛出。
  • errorHandler为全局钩子,使用Vue.config.errorHandler配置,接收参数与errorCaptured一致,2.6后可捕捉v-onpromise链的错误,可用于统一错误处理与错误兜底。

Vue.observable你有了解过吗?说说看

参考答案 让一个对象可响应。可以作为最小化的跨组件状态存储器。

Vue项目中如何配置favicon?

参考答案
  • 静态配置 <link rel="icon" href="<%= BASE_URL %>favicon.ico">, 其中<%= BASE_URL %>等同vue.config.js中publicPath的配置;
  • 动态配置<link rel="icon" type="image/png" href="">
    import browserImg from 'images/kong.png';//为favicon的默认图片
    const imgurl ='后端传回来的favicon.ico的线上地址'
    let link = document.querySelector('link[type="image/png"]');
    if (imgurl) {
        link.setAttribute('href', imgurl);
    } else {
        link.setAttribute('href', browserImg);
    }
    

怎么修改Vue项目打包后生成文件路径?

参考答案
  • 在Vue CLI2中修改config/index.js文件中的build.assetsPublicPath的值;
  • 在Vue CLI3中配置publicPath的值。

怎么解决Vue项目打包后静态资源图片失效的问题?

参考答案

在项目中一般通过配置alias路径别名的方式解决,下面是Vue CLI3的配置。

configureWebpack: {
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            '@': resolve('src'),
            'assets': resolve('src/assets'),
            'css': resolve('src/assets/css'),
            'images': resolve('src/assets/images'),
        }
    },
},

怎么解决Vue中动态设置img的src不生效的问题?

参考答案

因为动态添加src被当做静态资源处理了,没有进行编译,所以要加上require。

<template>
    <img class="logo" :src="logo" alt="公司logo">
</template>
<script>
export default {
    data() {
        return {
            logo:require("assets/images/logo.png"),
        };
    }
};
</script>

在Vue项目中如何引入第三方库(比如jQuery)?有哪些方法可以做到?

参考答案
  • 先在主入口页面 index.html 中用 script 标签引入<script src="./static/jquery-1.12.4.js"></script>,如果你的项目中有用ESLint检测,会报'$' is not defined,要在文件中加上/* eslint-disable */
  • 先在主入口页面 index.html 中用 script 标签引入<script src="./static/jquery-1.12.4.js"></script>,然后在webpack 中配置一个 externals,即可在项目中使用。
    externals: {
        'jquery': 'jQuery'
    }
    
  • 先在webpack中配置alias,最后在main.js中用import $ from 'jquery',即可在项目中使用。
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            '@': resolve('src'),
            'jquery': resolve('static/jquery-1.12.4.js')
        }
    }
    
  • 在webpack中新增一个plugins,即可在项目中使用
    plugins: [
             new webpack.ProvidePlugin({
                 $:"jquery",
                 jQuery:"jquery",
                 "windows.jQuery":"jquery"
             })
         ]
    

其他Vue系列面试题