vue面试二十问
这是我自己在准备面试的时候,上网收集汇总的一些知识点,不能说你会这些就能找到自己心仪的工作,但你要想找到心仪的工作就必须会这些!
一、什么是MVVM?
MVVM是Model-View-ViewModel的缩写。Model 层代表数据模型;View 代表DOM,ViewModel 是一个同步View 和 Model的对象。
View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
优点:需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
二、vue的优点是什么?
-
低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
-
组件化,实现html的封装和重用
-
虚拟DOM,不在使用原生DOM操作
-
运行速度快
三、组件之间的传值?
1.父组件向子组件传值:
使用自定义属性绑定(v-bind:)相应的父组件数据 ,把自定义属性在子组件的props属性里定义一下。
<sonCom v-bind:parentmsg="msg"></sonCom>
//子组件props属性
props:['parentmsg']
2.子组件调用父组件方法并传参(变向的子组件向父组件传值)
通过$emit
//父组件方法
methods:{
show(data){
console.log(data)
}
}
//DOM
<sonCom v-on:func = 'show'></sonCom>
//子组件
this.$emit('func',123)//123可以改成子组件data中的数据,完成子组件向父组件传值
3.兄弟组件传值
通过event bus ,大项目vuex
1.创建一个空vue实例,并将它发布到全局中(bus.js文件)
import Vue from 'vue'
const bus = new Vue()
export default bus
2.在子组件一中引用bus.js,用bus.$emit(‘参数name’,value)方法传参
3.在子组件二中引用bus.js,用bus.$on(‘参数名字’,callback(value))方式接受参数并处理
四、为什么组件中的data必须是function?
组件中的data写成一个函数,数据以函数返回值的形式定义,保证每个组件实例都有自己的私有数据空间,不会造成污染。要不然所有组件实例共用一个data,改一个其他全改
五、v-show、v-if 区别,使用场景
v-show:本质控制css的display:none,只编译一次。切换开销小,初始开销大
适用场景:频繁切换某节点
v-if:本质动态向DOM树添加或者删除DOM元素,不停销毁和创建比较消耗性能。初始渲染开销小,切换开销大
适用场景:不需要频繁切换节点
六、获取DOM
ref=“domName”,this.$refs.domName
七、为什么要使用key?
需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。作用主要是为了高效的更新虚拟DOM
八、双向数据绑定
(大致)
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调
核心:Object.defineProperty()(详看红宝书)
手写一个简单的双向数据绑定
let input = document.getElementById("input");
let text = document.getElementById("text");
let data = { value: "" };
Object.defineProperty(data, "value", {
set: function (val) {
text.innerHTML = val;
input.value = val;
},
get: function () {
return input.value;
}
});
input.onkeyup = function (e) {
data.value = e.target.value;
};
九、computed和watch的区别
computed:
-
支持缓存,只有依赖数据发生改变,才会重新进行计算.
-
不支持异步,当computed内有异步操作时无效,无法监听数据的变化.
适用场景:一个属性受多个属性影响(购物车商品结算)
watch:
-
不支持缓存,数据变,直接会触发相应的操作;
-
watch支持异步;
-
监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
适用场景:当需要在数据变化时执行异步或开销较大的操作时(搜索数据)
十、常用事件修饰符
- .stop():等同于JavaScript中的event.stopPropagation(),防止事件冒泡。
- .prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为。
- .capture:与事件冒泡的方向相反,事件捕获由外到内。
- .self:只会触发自己范围内的事件,不包含子元素
- .once:只会触发一次
十一、vue-router中的导航钩子
-
全局 钩子
const router = new VueRouter({ ... }); router.beforeEach((to, from, next) => { // do someting }); //to: Route,代表要进入的目标,它是一个路由对象 //from: Route,代表当前正要离开的路由,同样也是一个路由对象 //: Function,这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数
-
路由独享钩子
cont router = new VueRouter({
routes: [
{
path: '/file',
component: File,
beforeEnter: (to, from ,next) => {
// do someting
}
}
]
});
- 组件内的导航钩子
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
// 导航离开该组件的对应路由时被调用
}
}
十二、vue的两个核心
- 数据驱动
viewModel,保证数据和视图的一致性
-
组件系统
组件化开发,优点很多,可以很好的降低数据之间的耦合度。将常用的代码封装成组件之后,就能高度的复用,提高代码的可重用性。一个页面/模块可以由多个组件所组成。
十三、Vue生命周期
/*创建阶段*/
//1.第一个生命周期函数
beforeCreate() {//表示实例完全被创建出来之前,会执行它
// console.log(this.msg);
// this.show();
/*
在beforeCreate生命周期执行的时候,data和methods中的数据还没有初始化
*/
},
//2.第二个生命周期函数
created() {
console.log(this.msg + '--->created');
this.show();
//在created中,data,和methods 已经被初始化了
},
//3.第三个生命周期函数
beforeMount() {
//表示 模板已经在内存中编译完成了,但是尚未把模板渲染到页面中
console.log(document.getElementById("me").innerHTML + '--->beforeMount')
//在beforeMount 执行的时候,页面中元素还没有被真正替换过来,只是之前写的一些模板字符串
},
//4.第四个生命周期函数
mounted() {
//表示 模板已经在内存中编译完成了,但是已经把模板渲染到页面中
console.log(document.getElementById("me").innerHTML + '--->mounted')
//mounted 执行的时候,页面中元素已经被真正替换过来
//只要执行完mounted,就表示整个Vue实例已经初始化完毕了,此时组件已经脱离了创建阶段,进入了运行阶段。
},
/*运行阶段*/
//1.第一个运行生命周期函数
beforeUpdate() { //这时候表示我们的界面还没有被更新,【数据更新了】
console.log('页面中msg的值' + document.getElementById("me").innerHTML + '--->beforeUpdate')
console.log("Data中msg值为:" + this.msg)
//数据是最新的,页面没有同步
},
//2.第二个运行生命周期函数
updated() {
console.log('页面中msg的值' + document.getElementById("me").innerHTML + '--->updated')
console.log("Data中msg值为:" + this.msg)
//数据是最新的,页面已经同步
},
/*销毁阶段*/
//1.第一个销毁生命周期函数
beforeDestroy() { //Vue实例从运行到销毁阶段
//Vue实例上所有的Data和所有的methods,以及过滤器、指令。。。都处于可用状态,此时还没有真正执行销毁过程
},
//1.第二个销毁生命周期函数
destroyed() {
//Vue实例上所有的Data和所有的methods,以及过滤器、指令。。。都已经销毁。
},
});
十四、vuex
1.Vuex是什么
Vuex是实现组件全局状态(数据)管理的一种机制,可以方便实现组价之间的数据的共享
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28jCMlgP-1599554454926)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1598779618877.png)]
- Vuex优点
- 能够在Vuex中集中管理共享的数据,易于开发和后期维护
- 能够高效的实现组件之间的数据共享,提高开发效率
- 储存在vuex中的数据都是响应式的,能够实时保持数据与页面的同步
-
Vuex四个核心概念
-
State
State提供唯一的公共数据源,所有共享的数据都要统一放到Store的State中进行存储。state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据发生改变,依赖这个数据的组件也会发生更新
组件中访问:
//方式一: this.$store.state.myDate //方式二: import { mapState } from 'vuex' computed:{ ...mapState(['myData']) }
-
Mutation
Mutation用于变更Store中的数据,可以集中监控数据变化(不能写异步代码)
//store.js中 mutation:{ func(state,argument){ fn() } } //组件中调用 this.$store.commit('func',argument) //第二种方法 import { mapMutations } from 'vuex' methods:{ ...mapMutations(['func']), myfunc(){ this.func(argument) } }
-
Action
如果通过异步操作变更数据,必须通过Action,而不能使用Mutation,但是在Action 中还是要通过触发Mutation的方式间接变更数据
//store.js中 actions:{ func(context,argument){ setTimeout(()=>{ context.commit('mutation里的function',argument ) },wait) } } //组件中 this.$store.dispatch('func',argument) //方式二: import { mapActions } from 'vuex' methods:{ ...mapActions(['func']), myfunc(){ this.func(argument) } }
-
Getter
用于对Store中的数据进行加工处理形成新的数据。Store数据改变,Getter的数据也会跟着变化(像vue里的计算属性)
getters:{ newData(state){ return `新值为${state.myData}` } } //方式一: this.$store.getters.newData //方式二: import { mapGetters } from 'vuex' computed:{ ...mapGetters(['newData']) }
-
modules
在Vue中State使用是单一状态树结构,应该的所有的状态都放在state里面,如果项目比较复杂,那state是一个很大的对象,store对象也将对变得非常大,难于管理。
module:可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB})
-
十五、单页面应用(SPA)
SPA->就是只有一个主页面的应用,浏览器一开始要加载所有必须的 html、js、css。交互时候由路由程序动态载入,单页面的页面跳转仅刷新局部资源,多用于pc端
优点:
- 良好的交互体验:单页应用的内容的改变不需要重新加载整个页面。
- 减轻服务器压力:服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。
- 良好的前后端分离:后端不用负责模板渲染,只需要进行数据的存储与计算
缺点:
- 对SEO不太友好,不利于seo的搜索
- 需要自行实现导航的前进后退;
- 页面复杂度高、初次加载耗时多
十六、虚拟DOM
- 什么是虚拟DOM?
虚拟 dom 是相对于浏览器所渲染出来的真实 dom 的,在react,vue等技术出现之前,我们要改变页面展示的内容只能通过遍历查询 dom 树的方式找到需要修改的 dom 然后修改样式行为或者结构,来达到更新 ui 的目的。
这种方式相当消耗计算资源,因为每次查询 dom 几乎都需要遍历整颗 dom 树,如果建立一个与 dom 树对应的虚拟 dom 对象( js 对象),以对象嵌套的方式来表示 dom 树,那么每次 dom 的更改就变成了 js 对象的属性的更改,这样一来就能查找 js 对象的属性变化要比查询 dom 树的性能开销小。
- 为什么DOM性能开销大?
其实并不是查询 dom 树性能开销大而是 dom 树的实现模块和 js 模块是分开的这些跨模块的通讯增加了成本,以及 dom 操作引起的浏览器的回流和重绘,使得性能开销巨大,原本在 pc 端是没有性能问题的,因为 pc 的计算能力强,但是随着移动端的发展,越来越多的网页在智能手机上运行,而手机的性能参差不齐,会有性能问题。
- vue大致解决方法
每次更新 dom 都尽量避免刷新整个页面,而是有针对性的去刷新那被更改的一部分、也就是diff算法
- diff算法的大致思路
我们先根据真实DOM生成一颗virtual DOM
,当virtual DOM
某个节点的数据改变后会生成一个新的Vnode
,然后Vnode
和oldVnode
作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode
的值为Vnode
。diff的过程就是调用名为patch
的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。
具体实现可以参考https://www.cnblogs.com/wind-lanyan/p/9061684.html
十七、vue-router的两种模式
1、hash ——即地址栏URL中的#符号(此hsah 不是密码学里的散列运算)。 比如这个URL:http://www.abc.com/#/hello, hash 的值为#/hello。它的特点在于:hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
2、history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法。(需要特定浏览器支持) 这两个方法应用于浏览器的历史记录站,在当前已有的back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改是,虽然改变了当前的URL,但你浏览器不会立即向后端发送请求。history模式,会出现404 的情况,需要后台配置。—>利于seo
十八、Keep-alive组件
组件切换的时,保持这些组件的状态,以避免反复重渲染导致的性能问题(Vue 会创建了一个新的实例)
十九、Slot插槽
插槽含义:就是引入子组件后,在插入子组件元素中添加信息或者标签,使得子组件的指定位置插入信息或者标签
插槽有三种:默认插槽、具名插槽、作用域插槽
二十、nextTick
在Vue生命周期的created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中
changeMsg() {
this.msg = "Hello world."
this.msg1 = this.$refs.msgDiv.innerHTML //原来内容
this.$nextTick(() => {
this.msg2 = this.$refs.msgDiv.innerHTML //Hello world
})
this.msg3 = this.$refs.msgDiv.innerHTML//原来内容
}
//其根本原因是因为Vue中DOM更新是异步的
)
十九、Slot插槽
插槽含义:就是引入子组件后,在插入子组件元素中添加信息或者标签,使得子组件的指定位置插入信息或者标签
插槽有三种:默认插槽、具名插槽、作用域插槽
二十、nextTick
在Vue生命周期的created()
钩子函数进行的DOM操作一定要放在Vue.nextTick()
的回调函数中
changeMsg() {
this.msg = "Hello world."
this.msg1 = this.$refs.msgDiv.innerHTML //原来内容
this.$nextTick(() => {
this.msg2 = this.$refs.msgDiv.innerHTML //Hello world
})
this.msg3 = this.$refs.msgDiv.innerHTML//原来内容
}
//其根本原因是因为Vue中DOM更新是异步的