vue2面试题
1.阐述事件捕获与事件冒泡
- 事件的概念:事件,是文档或浏览器窗口中发生的一些特定的交互瞬间。事件流,指的是事件发生的顺序。
- 事件捕获:由外向内,事件从最外层向目标元素传播
- 事件冒泡:由内向外,事件从目标元素向最外层传播
- 阻止事件冒泡的方法:event.stopPropagation()
- 阻止事件默认行为:比如a链接点击会自动打开超链接,event.preventDefault();
2.页面回流与重绘
-
浏览器的渲染过程
解析HTML,生成DOM树,解析CSS,生成CSS树,将DOM树与CSS树结合起来生成渲染树。根据生成的渲染树,进行页面回流,得到元素的几何信息(位置,大小),根据得到渲染树和几何信息,得到具体的像素点,进行页面的重绘。将像素发给GPU,在页面上进行显示。 -
回流
构造了渲染树,可以将DOM元素与它对应的样式结合起来,得到对应dom元素在视口内的位置和大小。只要页面布局发生变化就会触发回流 -
重绘
上面我们知道了渲染树以及回流知道了对应元素的具体位置及大小信息,得到具体的像素值这时候将渲染树上的节点根据大小位置信息,渲染到页面上,叫做重绘。只要改变某个元素的背景色,文字颜色,边框颜色等不影响它周围或内部布局的属性,就会发生重绘 -
性能优化
尽量减少页面的回流和重绘
3.输入url到浏览器上进行页面展示发生了什么
- DNS域名解析:DNS将域名解析成ip地址
- 建立TCP连接(三次握手):三次的目的是建立可靠的通信
- HTTP请求:浏览器将http请求数据发给服务器(客户端–>服务器)
- HTTP响应:服务器收到客户端的请求,返回响应结果给到浏览器(服务器–>客户端)
- 关闭TCP链接:数据传输完成后,通过第四次握手终止连接
- 页面渲染:浏览器解析响应结果,进行页面渲染
4.px、em、rem、%、vw、vh
- px 绝对像素单位
- em 参考父元素的font-size的大小
- rem 相对于根结点html的font-size的大小
- % 相对长度单位,相对于父元素的尺寸的取值
- vw 相对于浏览器视口宽度的单位
- vh 相对于浏览器视口高度的单位
5.盒子模型
- border-box:盒子的宽高 = width + padding + border
- content-box 盒子的宽高 = width
6.BFC
-
概念:BFC(块级格式化上下文)是一块独立渲染的区域,独立且不会影响外部元素
-
怎样形成BFC:
1.设置浮动float
2.设置定位,absoulte或者fixed
3.行内块显示模式,inline-block
4.设置overflow,即hidden,auto,scroll
5.表格单元格,table-cell
6.弹性布局,flex -
利用BFC解决什么问题
1.解决margin塌陷(垂直方向)
2.清除浮动
3.BFC可以阻止标准流元素被浮动元素覆盖
7.div垂直水平居中
- 水平居中
1.为该行级元素的父元素设置text-align:center配合line-height样式
2.定宽块级元素:为其自身设置margin:auto样式 - 水平垂直居中
1.利用flex布局使内部块级元素水平,垂直居中(display:flex;justify-content: center; align-items:center;)
2.利用定位实现,父元素:position:relative; ,子元素:position: absolute;top:50%;left:50%;transform:translate(-50%,-50%);
3.父元素:position:relative; ,子元素:position: absolute; left:50%,top: 50% + margin-left:-(自身宽度的一半),margin-top:-(自身高度的一半)
4.grid布局 父元素设置display:grid,justify-content:center,align-content:center,grid-templaet-columns:100px,grid-template-rows:100px
8.js数据类型以及类型判断
-
数据类型
1.基本数据类型:Number、Boolean、String、undefined、null
2.复杂数据类型:数组、对象、函数 -
数据类型判断方法
1.typeof 可以检测string、Boolean、number、undefined、function,其中null和array(检测出来是object),原因在于,null和数组被当作一个空对象去引用
2.instanseof(推荐使用)
用来判定对象的具体类型,用法:a instanse of A ,意思是判断a是否是A的实例,返回值是布尔值
3.Array.isArray(需要检测的数组)可以检测一个数据是否是数组
4.Object.prototype.toString.call(),会返回一个形如 “[object XXX]” 的字符串
9.v-for中为何要使用唯一的key
1.若使用数组index当作key,当向数组中新插入一个元素后,这时会更新索引,对应着后面的虚拟dom的key都会全部更新,这些更新都是不必要的
2.如果没有key的话,默认的是就地复用的策略,如果数据项的顺序发生改变,vue不是移动dom来匹配数据项的改变,而是简单复用原来位置的每个元素,当比较到这个元素的值不一样的时候,将新的值放到该位置,以此类推,key的作用主要是为了高效的更新虚拟DOM
10.px2rem禁止某些px转rem
#box {
height: 44px //用postcss-px2rem插件配置后相当于0.44rem
width: 100%
font-size: 24px;/*no*/ //如果不想用插件转换可以用/*no*/标识符
}
11.vue的生命周期
创建:beforecreate、created
beforecreate:
第一个钩子,这个阶段的data、methods、computed以及watch的数据和方法不能被访问
created:
这个阶段完成数据观测,数据初始化完成,可以使用数据,更改数据
无法与dom进行交互,想要的话可以用过nextTick来访问,nextTick延迟回调,更新数据后立即操作dom
挂载:beforeMount、mounted
beforeMount:
发生在页面渲染之前,当前阶段虚拟Dom已经创建完成,即将开始渲染。
在此时也可以对数据进行修改,不会触发updated
mounted:
数据挂载到dom上,数据完成双向绑定,可以访问到dom节点,使用$refs属性对dom进行操作
补:第一次页面加载就会触发 创建和挂载钩子
更新 beforeUpdate、updated
beforeUpdate:
发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发
可以在当前阶段进行更改数据,不会造成重复渲染
uodated:
发生在更新完成以后,当前阶段组件dom已完成更新
需注意的是避免在此期间更改数据,因为这可能导致无限循环的更新
销毁 beforeDestory、destroyed
beforeDestory:
发生在市里销毁之前,在当前阶段实例完全可以被使用
可以在这进行善后收尾工作,如清楚定时器
destroyed:
发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被清除,监听被移出,子实例也统统被销毁
补充:
activited:
keep-alive专属,组件被激活时调用
deactivited:
keep-alive专属,组件被销毁时调用
12.v-show与v-if的区别
v-if 只有条件为真的时候才会真正渲染标签,不适合频繁渲染
v-show 基于display切换 适合频繁渲染
不推荐v-if与v-for使用,因为会把每个元素都增加v-if,造成性能问题
13.组件间通信
1.父传子:
父组件使用 :参数名=要传递的参数 :num=1000
子组件使用props接收 (props:[属性名])
子组件接收的另一种方式:
props:{
num:{
type:NUmber,//数据类型
required:true,//必须传值
default:{
100
},//默认携带的数字
}
}
2.子传父
1.**传递**:在调用子组件的位置,添加自定义事件,子组件使用$emit传递数据
3.父组件调用子组件的方法获取子组件的参数
父组件上面定义ref="child"
父组件里调用子组件的方法:this.$refs.child.showPhone() showPhone是子组件里面的方法
父组件里获取到子组件的参数:this.$refs.child.phone phone是子组件里的变量
4.子组件直接调取父组件数据参数和方法
子组件中获取数据参数:this.$parent.parentData parentData为父元素的变量
子组件中获取方法:this.$parent.showParent() showParent为父元素的方法
14.computed和watch的区别:
1.computed属性
1.支持缓存,只在依赖的数据发生变化时,才会重新计算,得到的为一个数据结果
2.不支持异步,当computed内有异步操作时无效,无法监听数据的变化
3.使用方式
- 模版
用插值表达式{
{
计算属性名}}
- 在实例内
this.计算属性名
代码演示:
{
data(){
},
methods:{
},
computed:{
计算属性名1(){
//对依赖的属性进行处理,且进行return
return
},
计算属性2(){
//对依赖的属性进行处理,且进行return
return
}
}
}
2.watch监听
1.不支持缓存,每调用一次就计算一次
2.支持异步
3.用于监听data里面的数据是否被修改,一旦修改就可以执行一些其他的操作【方法】
4.在监听的时候,可以有二次参数,第一次参数为新数据,第二次为旧数据
5.高级监听:可以监听单数据、数组,但是当监听对象的时候,明明数据修改了,却没有触发监听,此时需要开启深度监听,watch只会监听第一层
{
data:{
},
watch:{
//监听器的作用就是监听数据是否发生了变化,变化后可以进行一些其他的操作
//只要没有发生变化,就没有办法进行其他操作
text(newValue,oldValue){
},
deep: true, // 是否深度监听
immediate: true, // 是否在组件创建时立即执行回调函数
}
}
15.vue中的$set方法
1.由于vue会在初始化实例的时候进行双向数据绑定,所以属性必须是在data对象存在才能让他响应,如果要给对象添加新的属性,此时新属性并没有进行上述过程,不是响应式的,所以会出现数据变化,页面不变的情况,此时需要用到$set
2.实例
myInfo:{
name:'xiaohua',
age:'18'
}
this.$set(this.myInfo,'age',24)
16.vue路由
1.概念:路由指的是应用程序中的一个页面,是一个js对象
2.vue-router路由模式有几种?
- hash路由:在地址栏会有一个#号,hash发生变化的url都会被浏览器记录下来,浏览器的前进后退可以用
- history路由:
history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法。提供了对历史记录进行修改的功能。它能让开发人员在不刷新网页的情况下改变站点的 URL。History 有 URL 重定向问题,需要在服务端去配置 url 重定向,否则会报 404 错误。
3.router与route的区别
- $router 路由实例,用来操作路由,包括push、replace、go、forward等方法,可以动态更改url,从而实现页面间的无刷新跳转
- $route 路由信息对象 包括URL 路径、查询参数、路径参数等信息,path、params、query参数,只读对象
4.params传参与query传参
- params传参类似于post请求,参数不会写到地址栏中(但是不能刷新),params只能配合name使用,假如使用动态路由'/user:id',则可以解决这个问题。
- query传参类似于get传参,传过去的参数会显示在地址栏中,query既可以配合name使用,又可以配合path使用
- 接收参数this.$route.query.id this.$route.params.id
// 路由配置
{
path: '/user',
component: User,
props: {
id: userId }
}
然后再目标页面里使用props去接收参数
// 在目标组件中获取传递的参数
export default {
props: ['id'],
created() {
console.log(this.id)
}
}
需要注意的是,在使用属性传参时,必须在目标组件中声明`props`,否则会抛出警告。同时,在使用属性传参时,可以使用`props: true`来将路由参数自动注入到目标组件的props中。
5.路由跳转
- 声明式路由router-link to=`/path?参数名=参数值`
- 编程式路由跳转
this.$router.push({
name:'hello',query:{
id:1}})
this.$rouer.push({
name:'hello',params:{
id:1}})
this.$router.push({
path:'/hello',query:{
id:1}})
6.vue路由重定向
const routes = [
{
path: '/', redirect: '/index'},
{
path: '/index', component: index }
]
7.路由守卫
to为目标路由、from为当前路由 next()为跳转 可以用来做登录拦截
router.beforeEach((to, from, next) => {
document.title = to.meta.title || '卖座电影';
if (to.meta.needLogin && !$store.state.isLogin) {
next({
path: '/login'
})
} else {
next(
{
path:'/',
query:{
redirect: to.fullPath
}
})
}
})
8.路由懒加载
- 为什么需要懒加载,像vue这种单页面应用,如果没有懒加载,运用webpack打包后的文件将会异常的打,造成首页加载长时间的白屏,并且加载时间长。运用懒加载可以减少首页加载用时
{
path: '/login',
name: 'Login',
component: () => import('../views/login/login.vue')
},
9.路由监听
watch:{
$route(to,from){
to代表的是当前路由,from指的是上一个页面路由,监听到路由变化后可以拿参数等东西 比如to.query.name等 }}
17.vue的内置组件keep-alive
页面切换的时候会进行销毁,当我们不想让它销毁的时候就需要用keep-alive包裹着组件
在组件切换过程中将状态保留在内存中,防止重复渲染dom,减少加载时间以及性能消耗,提高用户体验
根据条件缓存页面
APP.js页面
<keep-alive>
<!-- 添加meta的则添加 keep-alive -->
<router-view v-if="$route.meta.keep"></router-view>
</keep-alive>
<!-- 不添加 -->
<router-view v-if="!$route.meta.keep"></router-view>
路由页面给需要的添加meta
{
path: '/',
name: 'home',
alias: '/home',
components:{
home },
meta:{
keep:true //需要缓存
}
},
前端逻辑梳理
1.后端返回msg、stream、audioSrc
data:msg,audioList:[] (组件上接收这个音频参数列表,当第一个音频播放完以后自己去拉)
let obj = {
user_id:1,
message:this.data.msg + receivedData.data.content,
kind:kind == 99 ? 0 : kind,
buttons:kind == 4 ? buttons_list : receivedData.data.buttons,
type:'text',
chatType:'audio',
stream:stream
}
this.data.videoContext1.stop() //停止视频播放
let messgaeListArray = this.data.messageList
if(stream == 'begin'){
this.data.msg = receivedData.data.content
this.data.audioList = []
obj.audioSrc = receivedData.audioSrc,
messgaeListArray.push(obj)
this.setData({
answerData:receivedData.data,
// inputShow:false,
messageList:messgaeListArray,
audioSrc:res.audioSrc,
isOrgin:false,
isAudio:'audio',
defaultVideo:false,
})
}else if(stream == 'middle'){
this.data.audioList.push(receivedData.audioSrc)
messgaeListArray[messgaeListArray.length - 1] = obj
this.setData({
messageList:messgaeListArray
})
}else if(stream == 'stop'){
this.data.audioList.push(receivedData.audioSrc)
messgaeListArray[messgaeListArray.length - 1] = obj
this.setData({
messageList:messgaeListArray
})
}
//子组件
if(this.data.chatType == 'audio' && this.data.stream == 'begin'){
if(this.data.myaudio){
this.data.myaudio.stop()
}
this.openAudio()
}
//第一个音频播放完的操作
this.data.myaudio.onEnded(() => {
this.data.audioList.forEach(item=>{
this.data.myaudio.src = item
this.data.myaudio.play()
})
if(this.data.stream == 'stop'){
this.triggerEvent('showTalkSilenceVideo')
}
// console.log('音频自然播放结束事件');
});