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节点进行一些需要的操作;
-
初始化组件的时候,仅执行beforeCreated/created/beforeMount/mounted四个钩子函数;
-
当改变data中定义的响应式变量时,会执行beforeUpadate/updated;
-
初始化和销毁时的钩子函数只会执行一次,beforeUpadate/updated可执行多次;
-
挂载的时候,子组件完成挂载后,父组件才会挂载;
注意:在传值的时候,如果父组件在mounted传给子组件值,子组件是没有办法在created里面使用的
father-beforeCreated –> father-created –> son-beforeCreated –>son-created –> son-mounted –>father-mounted(这是最后一个)
father-beforeDestroy –> son-beforeDestroy –> son-destroyed –> father-destroyed
-
当子组件完成挂载后,父组件会主动执行一次beforeUpdate/updated钩子函数(仅首次);
-
销毁父组件时,先将子组件销毁后,才会销毁父组件;
-
总结来说,虚拟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端的前端开发(特别是中后台产品)提供一个快速且灵活的解决方案。)
使用步骤:
- 首先
npm install animate.css --save
; - 然后在vue文件的script中引入
import animate from 'animate.css'
; - 最后绑定元素使用,如下:
<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[路由守卫]有哪些钩子函数,哪一个是可以在全局使用的
主要用来作用是拦截导航,让他完成跳转或取消。
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
有三种方式可以植入路由导航过程中:全局的;单个路由独享的;组件级的
①.全局导航钩子
- router.beforeEach 全局前置守卫 进入路由之前
- router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用
- 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*/}
}]
});
③.组件内的导航钩子:beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
。直接在路由组件内部直接进行定义的
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;
}