vue相关 面试问题

Posted by CodingWithAlice on July 12, 2019

vue相关 面试问题

3.vue的生命周期有哪些?它们有什么不同?

生命周期钩子 组件状态 响应类型 最佳实践
beforeCreate 实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods(都不存在)上的方法和数据 拿不到任何信息,无法篡改数据,一般做loding,这个时候的vue实例还什么都没有,但是$route对象是存在的,可以根据路由信息进行重定向之类的操作。 常用于初始化非响应式变量
created 实例创建完成,可访问data、computed、watch、methods上的方法和数据,还未挂载到DOM,不能访问到$el属性,$ref属性内容为空数组 $el,没有初始化,数据已加载完成,可以篡改数据,并更新,不会触发beforeUpdate,updated,在这结束loading,还做一些初始化,实现函数自执行 ,$ref属性内容为空数组;定义getter、setter存储器属性,在实例创建之后被调用,该阶段可以访问data,可以使用this。该阶段允许执行http请求操作。 常用于简单的ajax请求,页面的初始化
beforeMount 在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 $el已被初始化,数据已加载完成,阔以篡改数据,并更新,不会触发beforeUpdate,updated。将HTML解析生成AST节点,再根据AST节点动态生成渲染函数。相关render函数首次被调用(划重点)。 -
mounted 实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问 $el已被初始化,数据已加载完成,阔以篡改数据,并更新,并且触发beforeUpdate,updated,在这发起后端请求,拿回数据,配合路由钩子做一些事情;在挂载完成之后被调用,执行render函数生成虚拟dom,创建真实dom替换虚拟dom,并挂载到实例。可以操作dom,比如事件监听 常用于获取信息和操作,ajax请求
beforeUpdate 响应式数据更新时调用,发生在虚拟DOM打补丁之前 $vm.data更新之后,虚拟dom重新渲染之前被调用。在这个钩子可以修改$vm.data,并不会触发附加的重渲染过程。 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
updated 虚拟DOM重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作 虚拟dom重新渲染后调用,若再次修改$vm.data,会再次触发beforeUpdate、updated,进入死循环 避免在这个钩子函数中操作数据,可能陷入死循环
beforeDestroy 实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例   常用于销毁定时器、解绑全局事件、销毁插件对象等操作
destroyed 实例销毁后调用,调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁   -

注:

created和mounted之间的区别:

  • created和mounted中ajax请求的区别:created的时候视图未出现,请求较多的情况下,会出现白屏

  • created 是在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图,比如初始化、获取屏幕高度调整、赋值等等;

    而mounted是在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作;

  1. 初始化组件的时候,仅执行beforeCreated/created/beforeMount/mounted四个钩子函数;

  2. 当改变data中定义的响应式变量时,会执行beforeUpadate/updated;

  3. 初始化和销毁时的钩子函数只会执行一次,beforeUpadate/updated可执行多次

  4. 挂载的时候,子组件完成挂载后,父组件才会挂载;

    注意:在传值的时候,如果父组件在mounted传给子组件值,子组件是没有办法在created里面使用的

    father-beforeCreated –> father-created –> son-beforeCreated –>son-created –> son-mounted –>father-mounted(这是最后一个)

    father-beforeDestroy –> son-beforeDestroy –> son-destroyed –> father-destroyed

    图片描述

  5. 当子组件完成挂载后,父组件会主动执行一次beforeUpdate/updated钩子函数(仅首次);

  6. 销毁父组件时,先将子组件销毁后,才会销毁父组件;

  7. 总结来说,虚拟dom开始渲染是在beforeMount时,dom实例挂载完成在mounted阶段显示。那么接下来了解就是render函数。render函数最终返回的是createNodeDescription(节点描述),即俗称virtual node(虚拟节点)。

5. Axios调取数据?

[页面直接引用 <script src="https://unpkg.com/axios/dist/axios.min.js"></script>]

axios的特性

1.可以从浏览器中创建XHR对象 2、可以从nodeJS中创建HTTP请求 3、支持Promise 4、可以拦截请求和响应 5、可以转换请求数据和响应数据 6、可以取消请求 7、可以自动转换JSON数据 8、客户端支持防御XSRF

axios get 方法:仅仅请求后台数据

axios.get('index.php')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

aixos post方法:post请求更多的是要提交数据,params属性里的数据会出现在请求主体中。

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

多并发请求,一次性发几个请求

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // acct为第一个请求的结果,perms为第二个请求的结果
  }));

设置拦截器

//请求拦截器
axios.interceptors.request.use(  
    config => {
        btn.innerHTML='请求数据中';
        return config;
    },
    // 错误时发生的事情
    err => {
        console.log(err)
    });
// 响应应拦截器
axios.interceptors.response.use(
    config => {
        btn.innerHTML='请求数据成功';
        return config;
    },
    // 错误时发生的事情
    err => {
        console.log(err)
    });

设置自定义请求头

先安装Axios:npm install axios --save

再在main.js中引入Axios:

import axios from 'axios'
Vue.prototype.$http = axios;

即可在组件中调用Axios:

this.$axios.get('index.php/url')
  .then(response => {
    console.log(response)
  }).catch(error => {
    console.log(error)
  });

然后设置自定义的头请求:

axios.defaults.timeout = 5000;//请求超时的时间设定

axios.defaults.headers.post['Content-Type'] = 'application/json'; //axios默认的请求方式

axios.defaults.baseURL = 'http://localhost:8008';//axios默认的请求地址

axios.defaults.headers.common["token"] = "noname";//有些接口必须登录才可以调用,而登陆注册并未写好,后台给了一个故固定的token,写在了头里面

6. vue使用的UI框架?

animate.css是一款前端动画库,相似的有velocity-animate;

(element ui框架的按钮组件;

​ iView 是一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产品

​ Vuetify.js根据材料设计规格提供 UI 布局;

​ 基于 Vue.js 的 Bootstrap 组件;

​ cube-ui 是滴滴团队开发的基于 Vue.js 实现的精致移动端组件库;

​ vue-beauty 是一套基于 vue.js 和 ant-design样式 的PC端 UI 组件库;

​ AT-UI 是一个模块化的前端 UI 框架,基于Vue.js 的快速和强大的 Web 界面;专门为桌面应用程序构建;

​ Vue-Blu是基于Vue.js和Bulma开发的开源UI组件库。旨在为PC端的前端开发(特别是中后台产品)提供一个快速且灵活的解决方案。)

使用步骤:

  1. 首先npm install animate.css --save
  2. 然后在vue文件的script中引入import animate from 'animate.css'
  3. 最后绑定元素使用,如下:
<template>
    <div class="song">
        <p id="f" @click='fade'>hello</p>
    </div>
</template>
methods:{
    fade:function(){
        $('#f').addClass('animated bounceOutLeft')
    }
}

部分api常见:

fade: {
    title: '淡入淡出',
    fadeIn: '淡入',
    fadeOut: '淡出',
},
bounce: {
    title: '弹跳类',
    bounceIn: '弹跳进入',
    bounceOut: '弹跳退出',
},
zoom: {
    title: '缩放类',
    zoomIn: '放大进入',
    zoomOut: '缩小退出',

},
rotate: {
    title: '旋转类',
    rotateIn: '顺时针旋转进入',
},
flip: {
    title: '翻转类',
},
strong: {
    title: '强调类',
    bounce: '弹跳',
    flash: '闪烁',
}

7.v-if和v-show之间的区别?

​ 相同点:v-if与v-show都可以动态控制dom元素显示隐藏

​ 不同点:

实现本质方法区别

  • vue-show本质就是标签display:为none;,控制隐藏,DOM结构是一直存在的
  • vue-if是动态的向DOM树内添加或者删除DOM元素

编译的区别

  • v-show其实就是在控制css
  • v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

编译的条件

  • v-show都会编译,初始值为false,只是将display设为none,但它也编译了
  • v-if初始值为false,就不会编译了

性能

  • v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点

8.v-for中key值的作用?

​ key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

​ 在用v-for更新已渲染的元素列表的时候,会使用就地复用的策略;这就是说列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改了就重新渲染,不然就复用之前的元素。

9.vue中插槽的作用?

​ 插槽就是Vue实现的一套内容分发的API,将<slot></slot>元素作为承载分发内容的出口,没有插槽的情况下在组件标签内些一些内容是不起任何作用的。

​ 插槽内可以是任意内容。在<child-component>你好</child-component>内放置一些内容,输出内容还是在组件中的内容,直接在父组件的 <child-component>标签中定义的内容不会被渲染。在子组件template中加入<slot>元素占位,便能渲染父组件<child>标签下的内容

具名插槽,当需要多个插槽时,可以使用<slot>的特性:name。这个特性可以用来定义额外的插槽。

	<div id="root">
        <child>
            <header slot="header">header</header>
            <footer slot="footer">footer</footer>
        </child>
    </div>
       Vue.component('child',{
            template:`<div>
                            <slot name="header">default header</slot>
                            <div>content</div>
                            <slot name="footer">default footer</slot>
                        </div>`
            }
        )
        var vm=new Vue({
            el:'#root'
        })
 //输出结果是 header   content   footer

插槽默认内容 ,插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。如果父组件为这个插槽提供了内容,则默认的内容会被替换掉。

作用域插槽,作用域插槽就是父组件在调用子组件的时候给子组件传了一个插槽,这个插槽为作用域插槽,该插槽必须放在template标签里面,同时声明从子组件接收的数据放在一个自定义属性内,并定义该数据的渲染方式。(解决的问题:调用了两次child组件,因为调用的是同一个子组件,所以显示的内容完全一样。如何在每次调用时能有各自的渲染效果?)

<div id="root">
    <child>
        <template slot-scope="props"><!--该插槽必须放在template标签内-->
            <li></li> <!--定义渲染方式-->
        </template>
    </child>
    <child>
        <template slot-scope="props">
            <h1></h1><!--定义渲染方式-->
        </template>
    </child>
</div>
Vue.component('child',{
    data: function(){
        return {
            list:[1,2,3,4]
        }
    },
    template: `<div>
                    <ul>
                        <slot v-for="value in list" :value=value>//使用slot占位
                        </slot>
                    </ul>
                </div>`
})
var vm=new Vue({
    el: '#root'
})

10.vue中watch和computed有什么区别?分别在哪种场合下使用?监听的是data里面的值吗?

computed 计算属性 watch 观察的动作 methods
1. 数据会被缓存,只要依赖不发生改变,即使页面重新渲染,该方法也不会被调用 2.computed中的函数必须用return返回 1. 直接监测一个值的变化,监测值不发生变化,该方法就不会调用; 2. watch只会监听数据的值是否发生改变,而不会去监听数据的地址是否发生改变。也就是说,watch想要监听引用类型数据的变化,需要进行深度监听。 3.watch中的函数有两个参数,前者是newVal,后者是oldVal。 每次页面发生渲染,都会被重新调用
在computed中不要对data中的属性进行赋值操作。如果对data中的属性进行赋值操作了,就是data中的属性发生改变,从而触发computed中的函数,形成死循环了。 监听复杂数据类型需用深度监听(在被监听对象中使用handler);特殊情况下,watch无法监听到数组的变化,特殊情况就是说更改数组中的数据时,数组已经更改,但是视图没有更新。更改数组必须要用splice()或者$set  
使用场景:当一个值受多个属性影响的时候————购物车商品结算 使用场景:当一条数据的更改影响到多条数据的时候———搜索框  

computed和watch区别:

1、 功能上:computed是计算属性,也就是依赖其它的属性计算所得出最后的值,是用于定义基于数据之上的数据。watch是监听一个值的变化,然后执行对应的回调,是在某个数据变化时做一些事情

2、 是否调用缓存:computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而watch在每次监听的值发生变化的时候都会执行回调。

3、 是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return。

4、 如果一个值依赖多个属性(多对一),用computed肯定是更加方便的。如果一个值变化后会引起一系列操作,或者一个值变化会引起一系列值的变化(一对多),用watch更加方便一些。

11.数据请求应该添加在生命周期的哪里?

​ 看实际情况,一般在 created(或beforeRouter) 里面就可以,如果涉及到需要页面加载完成之后的话就用 mounted。主要的区分在于是否需要操作DOM结构。 ​ 1.在created的时候,视图中的html并没有渲染出来,所以此时如果直接去操作html的dom节点,一定找不到相关的元素; ​ 2.而在mounted中,由于此时html已经渲染出来了,所以可以直接操作dom节点,(此时document.getelementById 即可生效了);

12.vue组件中data为什么是函数

是因为js本身的特性带来的,跟vue本身设计无关。类比引用数据类型:Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;

//如果直接赋值,两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改
var MyComponent = function() {}
MyComponent.prototype.data = {
  a: 1,
  b: 2,
}
// 上面是一个虚拟的组件构造器,真实的组件构造器方法很多

var component1 = new MyComponent()
var component2 = new MyComponent()
// 上面实例化出来两个组件实例,也就是通过<my-component>调用,创建的两个实例

component1.data.a === component2.data.a // true
component1.data.b = 5;
component2.data.b // 5
Vue.component('my-component', {
  template: '<div>OK</div>',
  data() {
    return {} // 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
  },
})

data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响;

13.vue router[路由守卫]有哪些钩子函数,哪一个是可以在全局使用的

主要用来作用是拦截导航,让他完成跳转或取消

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

有三种方式可以植入路由导航过程中:全局的;单个路由独享的;组件级的

①.全局导航钩子

  1. router.beforeEach 全局前置守卫 进入路由之前
  2. router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用
  3. router.afterEach 全局后置钩子 进入路由之后
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => { 
    next();
});
router.beforeResolve((to, from, next) => {
    next();
});
router.afterEach((to, from) => {
    console.log('afterEach 全局后置钩子');
});

这三个参数 to 、from 、next 分别的作用:

to: Route,代表要进入的目标,它是一个路由对象;路由对象指的是平时通过this.$route获取到的路由对象

from: Route,代表当前正要离开的路由,同样也是一个路由对象

next: Function,这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数

next():进入该路由。进入管道中的下一个钩子,如果全部的钩子执行完了,则导航的状态就是 confirmed(确认的) ​ next(false):取消进入路由,这代表中断掉当前的导航,即 to 代表的路由对象不会进入,被中断,此时该表 URL 地址会被重置到 from 路由对应的地址(也就是将要离开的路由地址)。 ​ next(‘/’)next({path: ‘/’}):在中断掉当前导航的同时,跳转到一个不同的地址 ​ next(error):如果传入参数是一个 Error 实例,那么导航被终止的同时会将错误传递给 router.onError() 注册过的回调 注意:next 方法必须要调用,否则钩子函数无法 resolved

不同于前置守卫,后置钩子并没有 next 函数,也不会改变导航本身

②.路由独享的钩子:即单个路由独享的导航钩子,它是在路由配置上直接进行定义

const router = new VueRouter({
    routes: [{
            path: '/file',
            component: File,
            beforeEnter: (to, from ,next) => {/*do someting*/}
        }]
});

③.组件内的导航钩子beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave。直接在路由组件内部直接进行定义的

const File = {
    template: `<div>This is file</div>`,
    beforeRouteEnter(to, from, next) {
        // do someting
        // 在渲染该组件的对应路由被 confirm 前调用
    },
    beforeRouteUpdate(to, from, next) {
        // do someting
        // 在当前路由改变,但是依然渲染该组件是调用
    },
    beforeRouteLeave(to, from ,next) {
        // do someting
        // 导航离开该组件的对应路由时被调用
    }
}

注意:beforeRouteEnter 不能获取组件实例 this,因为当守卫执行前,组件实例被没有被创建出来,剩下两个钩子则可以正常获取组件实例 this;但是并不意味着在 beforeRouteEnter 中无法访问组件实例,我们可以通过给 next 传入一个回调来访问组件实例。在导航被确认是,会执行这个回调,这时就可以访问组件实例了:

//注意,仅仅是 beforRouteEnter 支持给 next 传递回调,其他两个并不支持。因为归根结底,支持回调是为了解决 this 问题,而其他两个钩子的 this 可以正确访问到组件实例,所有没有必要使用回调
beforeRouteEnter(to, from, next) {
    next (vm => {/*这里通过 vm 来访问组件实例解决了没有 this 的问题*/})
}

14.data为什么要用return?不是为了使每个组件有独立的数据?(摘自vue官方文档)

当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

15.vue中的定时器,一般在哪个生命周期中清除

在beforeDestroy()里面清除定时器,

beforeDestroy() {
    clearInterval(this.timer);        
    this.timer = null;
}