Vue2面试笔记
1. 为什么选择Vue?
Vue的核心是数据驱动视图,是渐进式、轻量级且非常容易上手的框架。
它是渐进式框架(可以帮助开发者更快更容易地构建和维护复杂的Web应用程序)核心思想是将Web应用程序分解成一系列可重用的模块,这些模块可以被多个应用程序共享,从而提高开发效率。支持组件化,虚拟DOM
它借助了
MVVM
设计模式思想,做双向数据绑定,有一套自己的数据响应式的系统。
2. Vue为什么不兼容IE8以下
Vue的核心双向绑定以及响应式原理都是基于
object.defineProperty
,而object.defineProperty
IE8及以下版本都不支持。Vue也需要
Promise
的支持,而IE8也并不支持。
3. 如何理解Vue单向数据流
简单来说:就是父组件通过props传递数据给子组件,所形成的一个单项的数据流。
为什么是单项的? 是由父组件传给自子组件的
因为如果子组件更新了props数据,会引起父组件以及与引用父组件数据的相关的组件数据的改变,页面也会随之而变,这就造成了数据严重的混乱以及不可控。
同时也是为了组件间更好的解耦
子组件如何更新父组件的值
通过emit自定义事件进行传值。
父组件可以传递一个方法给子组件
通过$parent方法来更改父组件的值
vuex共享仓库
4. vue中的key为什么不建议用index作为值
Vue中的Key是虚拟DOM的标识,当使用v-for渲染一个列表,当渲染原数据发生改变时,Vue会根据你的新数据生成最新的虚拟DOM,随后将新生成的虚拟DOM与旧的虚拟DOM进行对比,根据Key值看哪些数据可以复用,哪些不行需要生成新的。
虚拟DOM的对比规则(diff运算)
旧的虚拟dom找到新的虚拟dom根据相同key值进行对比
若两者虚拟dom内容相同,则直接复用之前的真实dom
若不同,则生成新的真实dom,随后用以替换掉之前的真实dom元素
旧的虚拟dom未找到新的虚拟dom
则直接生成新的真实dom渲染到页面上
用index作为key可能引发的问题
若对数据进行逆序添加,逆序删除等破环了index作为key值原本顺序的操作
会导致不能复用之前的真实dom,需要新生成。效率低下
若结构中还包含输入类的dom元素
会导致错误的dom更新,造成界面混乱。
开发中如何选择key值得数据
最好时用每条数据得唯一标识符,如id,学号,手机号,身份证号。
当不需要对数据进行逆序得添加以及删除得改动,等破坏顺序得操作,可以使用index值
5. Vue组件间通信
整理vue
中8种常规的通信方案
通过 props 传递
通过 $emit 触发自定义事件
使用 ref
EventBus
$parent或 $root
attrs 与 listeners
Provide 与 Inject
Vuex
5.1 父子组件之间通信
Props与$emit
父页面中,在子组件
child
的声明的标签上,声明属性并进行传递,子组件通过props来接收
// 在父组件中,定义num变量,并通过标签变量n传递给子组件 <child :n="num"> </child>
子组件接收父页面的值通过props有两种接收数据模式
可以通过数组的方式,默认不对传递来的数据做任何处理以及校验:
props:['n']
可以通过对象的方法,对父组件传递过来的数据进行验证。
// child.vue 子页面 // 1. 基础类型检查,检查n是否为数字类型,若不是,则控制台会报错 props: { n: Number, // 2. 多个类型检查 // 带有默认值的数字 propD: { type: Number, default: 100 }, // 自定义验证函数 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } },
子组件通过
$emit(事件名称,传递给父组件的数据)
事件传递数据给父组件进行修改。// child 子页面 methods:{ changeNum(){ this.$emit('numChange', '12') } }
// father 父页面 在页面中定义方法handleChange,接收子组件numChange事件的数据。 <child :n="num" @numChange="handleChange"> </child> methods:{ handleChange(num){ console.log(num) //12 } }
Ref
父修改子组件的值:在子组件上定义Ref,父组件可直接修改子组件中
data
的值,或者调用子组件中的方法,传值给子组件.<child :n="num" @numChange="changFatherNum" ref="childRef"> </child> methods: { changFatherNum(nn) { this.num = nn this.$refs.childRef.childN = 458 // 调用子组件的方法,并传值给子组件 this.$refs.childRef.changeSonNum('我传给你值') }, },
$parent:获得父组件实例上的所有属性以及方法。
可用于子组件修改父组件上的值。
子组件调用this.$parent.属性或方法进行修改。
this.$parent.grandPa = '我修改你的值了,笨B'
也可用于兄弟组件相互传值,将父组件当作跳转的平台eventBus。
兄弟组件
this.$parent.$emit('getInputValue', this.inputValue)
另一个兄弟组件
this.$parent.$on('getInputValue',this.getBrotherValue)
children:获得当前实例的直接子组件。需要注意 children 并不保证顺序,也不是响应式的。
子组件修改父组件的值,父组件可以将方法直接传递给子组件,子组件调用此方法传值,如:
父组件:
<template> <div> //传递给子组件自己的方法 <child :receive="receive"></child> </div> </template> export default { name:'father', methods: { receive(data){ console.log(data); } },
子组件
<template> <div> <input type="text" v-model="info" @keyup.enter="add"> 传值给父组件 </input> </div> </template> export default { name:'child', data(){ return{ info:'' } } props:['receive'] methods: { add(){ //调用父组件传来的方法,修改父组件的值 this.receive(this.info) } },
5.2 兄弟组件进行传值
使用
EventBus
,当作兄弟组件传值的一个中转站,因为Vue不能直接兄弟组件之间相互传值,需要一个媒介。定义一个
eventbus.js
文件
import vue from 'Vue' // 默认导出 const eventBus = new Vue() export default eventBus
兄弟组件导入eventbus这个js文件,并在触发事件后通过
bus.$emit(事件名字,数据)
上传自定义事件。另一个兄弟组件同样需要导入bus文件,并可以在
mounted方法中
,调用bus.$on(事件名字,回调函数)
<template> <div> <h2>{{uncleValue}}</h2> </div> </template> <script> import bus from './eventBus' export default { name:'uncle', data() { return { uncleValue: '' } }, mounted() { //接收兄弟组件的事件getInputValue,并且调用当前页面的getBrotherValue方法进行接收 bus.$on('getInputValue',this.getBrotherValue) }, methods: { getBrotherValue(data){ console.log(data); this.uncleValue = data } }, // 当组件被销毁时,调用off销毁自定义事件 beforedestory(){ bus.$off('getInputValue') } } </script>
注意:
当组件被销毁时,对应自定义的事件名(
bus.$emit()
)事件,也需要销毁在beforeDestroy生命周期组件中调用:
this.xxx.$off('getInputValue')
5.3 祖先组件与后代组件的通信
Provide与inject
Provide:可以是一个对象或返回一个对象的函数。该对象包含可注入其子孙的
property
。data() { return { grandPa: '我是你们的爹' } }, // Provide对象函数形式: provide(){ return{ foo: this.grandPa } }, // Provide对象形式,静态的: provide:{ foo: 'bar' }
Inject:可以是一个字符串数组,也可以是一个对象。用以接收祖先组件定义的
provide
里面的属性。子组件可通过this.foo
关键字访问到inject:['foo'], mounted() { console.log(this.foo); },
注意事项:
provide
和inject
绑定并不是可响应的。如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。子孙组件不可更改provide传给你们的值。
就是你传入数据给provide的方式不同,会影响到其在子组件中数据是否是可响应式的。
以对象的形式,以及computed形式传入给provide是可响应式的。直接传值不可响应就不会触发页面的更新。
// 祖先组件中的数据 data() { return { info: { grandma: '我是你men妈' // 在子组件是可响应式的,grandma的改变,其在子组件中也会实时响应 }, grandPa: '我是你们的爹' // 在子组件中不是可响应式的,这里改变不会影响到子组件 } }, provide(){ return{ foo: this.grandPa, parent:this.info } }, // 后代子孙组件 <h2>grandPa:{{foo}}</h2> <h2>{{parent.grandma}}</h2> inject:['foo','parent'],
6. Vue生命周期函数 详解过程
6.1 Vue生命周期
与其说是Vue生命周期,不如说其内组件的生命周期,就是一个组件从创建开始 经历了数据初始化,挂载,更新等步骤后,最后被销毁。
Vue生命周期分为三个阶段,挂载阶段,更新阶段,销毁阶段。
6.3 Vue生命周期执行顺序
挂载阶段
beforeCreate(创建前):
初始化了生命周期函数,以及v-pre、v-once指令
但数据监测(getter和setter)数据代理和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,实例上的配置项还未开始,也就是说不能访问到data、computed、watch、methods上的方法和数据。
Created(创建后):
初始化了数据代理以及数据监测,实例上
new Vue({ options })
配置的 options 包括 data、computed、watch、methods 等函数都配置完成。但是此时渲染的节点还未挂载到 DOM,所以不能访问到
$el
属性。
beforeMount(挂载前):
Vue开始解析模板,将你的模板生成虚拟DOM并存储在内存当中。
但其还没有生成真实DOM,所以在这个阶段所进行的一切操作DOM元素都无效,因为当挂载在页面上时,是将内存中的虚拟DOM转化为真实DOM(但Vue最好不要自己操作dom元素)
Mounted(挂载后):
将内存中的虚拟DOM转化为真实DOM并挂载到页面上去,Vue通过
vm.$el
拷贝了真实DOM,以便后面数据更新可以对DOM节点进行复用但注意在这个钩子函数尽量避免自己操作DOM元素,在此阶段可以进行发送网络请求,开启定时器,绑定自定义事件等。
更新阶段
beforeUpdate(更新前):
当data中的数据→响应式数据发生改变的时候调用,响应式数据更新完成,但是
但页面还是旧的,并没有发生改变。(页面和数据并没有保持同步)页面更新是异步的
Updated(更新后):
根据新数据 生成新的虚拟DOM,其与旧的虚拟DOM进行比较,看旧的DOM元素有没有可以复用的,随后对比完成,生成最新的真实DOM并挂载到页面上。
销毁阶段
beforeDestroy(销毁之前):
组件被销毁之前调用,这一步实例里面的data,methods仍然完全可用,
this
仍能获取到实例,但不建议在此阶段操作数据,因为此时所有对数据的修改,并不会再触发更新了在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。
destroyed(销毁后):
实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器、自定义事件会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
但绑定的点击事件,鼠标事件等原生js事件的方法任然可以调用,因为vue帮你绑定事件也是通过操作底层的js事件方法,销毁时并不会销毁这些方法。
7. 父子组件生命周期执行顺序
加载渲染阶段
父
beforeCreated
→ 父Created
→ 父beforeMount
子beforeCreate
→ 子create
→子beforeMount
→ 子mounted
注意事项:子组件挂载完成之后,父组件还未挂载完成,所以子组件不能够拿到在父组件mounted方法中获取的数据。
更新阶段
父
beforeUpdate
→ 子beforeUpdate
→ 子updated
→ 父updated
销毁阶段
父
beforeDestory
→ 子beforeDestory
→ 子destoryed
→ 父destoryed