Bootstrap

2020年Web前端面试题及答案----Vue篇

近年来,vue框架热度只增不减,几乎已经成为了前端开发人员必须掌握的框架。那么今天给大家带来vue的常见面试问题,如不详细或不正确的地方请大家在评论区补充纠正。

什么是MVVM?
MVVM分为Model、View、ViewModel三者。
Model 代表数据模型,数据和业务逻辑都在Model层中定义;
View 代表UI视图,负责数据的展示;
ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。

mvvm和mvc的区别?它和其他框架(JQuery)的区别是什么?哪些场景适合?
mvc和mvvm其实区别不大。都是一种设计思想,主要mvc中controller演变成mvvm中的ViewModel。mvvm主要解决了mvc中大量的DOM操作使页面渲染性能降低,加载速度变慢,影响用户体验
区别:vue数据驱动,通过数据来显示视图层而不是节点操作
场景:数据操作比较多的场景,更加便捷

Vue的优点是什么?

  1. 低耦合:视图View可以独立于Model变化和修改,一个ViewModel可以绑定到不同给的“view”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
    
  2. 可重用性:你可以把一些视图逻辑放在ViewModel里面,让很多view重用这段视图逻辑
    
  3. 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计
    
  4. 可测试:界面素来是比较难测试的,而现在测试可以针对ViewModel来写
    

Vue.js的两个核心是什么?
数据驱动、组件系统
Vue组件之间的传值
-父组件与子组件传值
父组件通过标签上定义传值(:父组件名称=”子组件props的名称”)
子组件通过props的方式接受数据
-子组件向父组件传递数据
子组件通过$emit方法传递参数给父组件

Vue-cli中怎么使用自定义组件,又遇到过哪些问题吗?

  1. 在components文件中创建组件.vue文件.是script中export default{}
    
  2. 在调用的页面中使用import XXX from 路径
    
  3. 在调用的组建components属性中注册组件,注意大小写(文档不接受-连字符,可以使用驼峰命名)
    

Vue如何实现按需加载配合webpack设置
webpack中提供了require ensure()来实现按需加载。以前引入路由是通过import这样的方式引入,现在改为 const定义的方式他引入。
不进行页面按需加载引入方式:import home from ‘…/XXX’
进行页面按需加载的引入方式:const home = ‘…/XXX’
v-show和v-if指令的共同点和不同点
v-show指令是通过修改元素的display的CSS属性让其显示或隐藏
v-if指令是直接销毁和重建DOM节点,达到让元素显示和隐藏的效果

如何让CSS只在当前组件中起作用?
在当前文档中使用style标签,并添加scope属性

keep-alive标签的作用是什么?
keep-alive标签包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染

聊聊Keep-alive的实现原理和缓存策略
原理:
keep-alive的实现正是用到了LRU策略, 将最近访问的组件push到this.keys最后面, this,keys[0] 也就是最久没有被访问到的组件, 当缓存实例超过max设置值, 删除this.keys[0]
LRU缓存淘汰算法: LRU算法根据数据的历史访问记录来进行记录, 其核心思想是”如果数据最近被访问过, 那么将来被访问的几率也更高”

Vue中引入组件的步骤

  1. 采用 ES6的import...from...语法或CommonJS的require()方法引入组件
    
  2. 对组件进行注册
    

// 注册

Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})
  1. 使用组件<my-component></my-component>
    

Vue常用的修饰符?
.precent:提交事件不在重载页面
.stop: 组织但即使单击事件冒泡
.self:当事件发生在该元素本身而不是子元素的时候会触发
.capture: 事件侦听, 事件发生的时候会调用

什么是Vue的计算属性?
在模板中放入太多的逻辑会让模板过重且难以维护, 在需要对数据进行复杂处理, 且可能多次使用的情况下, 尽量采用计算属性的方式。
好处:

  1. 使得数据处理结构清晰
  2. 依赖于数据,数据更新,处理结果自动更新
  3. 计算属性内容部this指向vm实例
  4. 在template调用时,直接写计算属性名即可
  5. 常用的getter方法,获取数据,也可以使用set方法改变数据
  6. 相较于methods, 不管依赖的数据变不变,methods都会重新计算, 但依赖数据不变的时候,computed从缓存中获取, 不会重新计算

Vue等单页面应用及其优缺点?
优点:Vue的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好
缺点:不支持低版本的浏览器, 最低只支持IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一点;不可以使用浏览器的导航按钮需要自行实现前进后退

指令v-el的作用是什么?
提供一个在页面上已存在的DOM元素作为Vue实例的挂载目标,可以是CSS选择器,也可以是一个HTMLElement实例

在Vue中使用插件的步骤
采用ES6的import…from…语法或CommonJS的require()方法引入插件
使用全局方法Vue.use(plugin)使用插件,可以传图一个选项对象Vue,use(MyPlugin,{someOption:true })

active-class是哪个组件的属性?
vue-router模块的router-link组件

说出至少4中vue当中的指令和它的用法?
v-if:判断是否为真,然后重组、销毁DOM节点
v-for:数据循环
v-bind:class 绑定一个属性
v-model:实现双向绑定
v-on:添加事件
v-else:配合v-if使用

vue-loader是什么?使用它的用途有哪些?
解析:vue文件的一个加载器
用途:js可以写es6、style样式可以scss、template可以加等

scss是什么?在vue-cli中安装使用步骤?有几大特性?
scss是css的预编译。
使用步骤:

  1. 先安装css-loader、node-loader、scss-loader等加载器模块
    
  2. 在build目录找到webpack.base.config.js,在那个extends属性中加一个拓展scss
    
  3. 在同一个文件,配置一个module属性
    
  4. 在组件的style标签上添加lang属性,lang=“scss”
    

特性:
可以使用变量($变量名=值);
可以用混合器
可以嵌套

Vue中的Key到底有什么用?
Key是给每一个vnode的唯一id, 依靠key, 我们diff操作可以更准确、更快速(对于简单列表页面渲染来说diff节点也更快,但会产生一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位)
diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对, 从而找到相应的旧节点
更准确: 因为带Key就不会出现重复现象, 在sameNode函数a.key === b.key对比中可以避免就地复用的情况。所以会更加准确,如果不加key会导致之前节点的状态被保留下来,会产生一系列的bug
更快速:key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度0(n),Map的时间复杂度仅仅为0(1)

谈一谈nextTick的原理
Vue的nextTick方法的实现原理

  1. Vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
  2. mictask因为其高优先级特性, 能确保队列中的微任务在一次事件循环前辈执行完毕
  3. 考虑兼容问题做了microtask向macrotask的降级方案

详解:
js执行是单线程的, 它是基于事件循环的。
主线程的执行过程就是一个tick,而所有的异步结果都是通过“任务队列”来调度。消息队列中存放的是一个个的任务(task)。规范中的task分为两大类, 分别是macro task和micro task,并且每个macro task结束后,都将清空所有micro task。
在浏览器环境中,常见的macro task 有 setTimeout、MessageChannel、postMessage、setImmediate
常见的micro task有 MutationObsever和Promise.then
Vue在更新DOM时是异步执行的,只要侦听到数据变化,Vue将开启一个队列, 并缓冲在同一事件循环中发生的所有数据变更。
在Vue2.5的源码中,macrotask降级的方案依次是: setImmediate、MessageChannel、setTimeout

为什么避免v-if和v-for用在一起?
当vue处理指令时,v-for比v-if具有更高的优先级,通过v-if移动到容器元素,不会再重复遍历列表中的每个值,取而代之的事,我们只检查它一次,且不会再v-if为否的时候运行v-for。

VNode是什么?虚拟DOM是什么?
Vue在羽绒棉上渲染的节点,及其子节点成为“虚拟节”,简写为“VNode”。“虚拟DOM”是由Vue组件树简历起来的整个VNode树的称呼
//这里可能有人不懂VNode是什么,介绍的也不详细,建议大家下来百度了解一下,面试一般不会问到这个

MINI UI是什么?怎么使用?说出至少三个组件的使用方法?
//基于vue框架的ui组件库很多,这里主要简单阐述一下组件的使用方法。
基于vue前端的组件库。使用npm安装,然后import样式和js;
vue.use(miniUi)全局引入。在单个组件局部引入;
import {Toast} form ‘mini-ui’;
组件一: Toast(‘登录成功’)
组件二: mint-header;
组件三:mint-swiper

自定义指定(v-check/v-focus)的方法有哪些?有哪些钩子函数?还有哪些钩子函数参数?
全局定义指令:在vue对象的directive方法里面有两个参数,一个是指令名称,另外一个是函数。组件内定义指令:directives钩子函数:bind(绑定事件触发)、inserted(节点插入的时候触发)、updata(组件内相关更新)
钩子函数的参数:el、binding

Vue封装组件的过程
首先,使用Vue.extend( )创建一个组件
再使用Vue.component( )方法注册组件
接着,如果子组件需要数据,可以在props中接受定义
最后,子组件修改好数据后,想把数据传递给父组件,可以使用emit()方法

Vue的双向数据绑定原理是什么?
vue.js是才用数据劫持介个发布-订阅者模式的方法,通过object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
observer: Vue中使用Observer类来管理上述响应式化Object.defineProperty的过程。它的作用是给对象的属性添加getter和setter,用来依赖收集和派发更新
Dep:用于收集当前响应式对象的依赖关系, 每个响应式对象包括子对象都拥有一个DEp实例(里面subs是Watcher实例数组),当数据有变更时,会通过dep.notify()通知各个watcher
Watcher: 观察者对象, 实例分为watch(render watcher),计算属性watcher(computed watcher),侦听器 watcher(user watcher)三种

Watch和Dep的关系
watcher中实例化了dep并向dep.subs中添加了订阅者, dep通过notify遍历了dep.subs通知每个watcher更新。
依赖收集

  1. initState时,对computed属性初始化时,触发computed watcher依赖收集
  2. initState时,对侦听属性初始化是,触发user watcher依赖收集
  3. render()的过程, 触发render watcher依赖收集
  4. re-render时,vm.render( )再次执行, 会溢出所有subs中的watcher的订阅, 重新赋值
    派发更新
  5. 组件对响应的数据进行了修改, 触发setter的逻辑
  6. 调用dep.notify( )
  7. 遍历所有的subs( Watcher实例 ),调用每一个watcher的update方法

简单描述每个周期具体适合哪些场景
生命周期钩子的一些使用方法:
beforecreate:可以在这加个loading事件,在加载实例时触发
created:初始化完成时的时间写在这里,如在这结束loading事件,异步请求也适宜在这里调用
mounted:挂载元素,获取到DOM节点
updated:如果对数据统一处理,在这里写上相应函数
nextTick:更新数据后立即操作DOM

打包Vue项目命令是什么?
npm run build

在Vue中同时发送多个请求怎么操作?

  1. 创建两个Promise,在Promise中使用axios
  2. 调用Promise.all([p1,p2]).then( res =>{ }).catch( err => { }) 方法
    举例说明:
getInfo(){
//创建promise,在promise中调用axios , then里使用resolve回调, catch里使用reject回调
var p1 = new Promie((resolve,reject) => {
this.$axios.get(httpUrl.getUser).then(res => {
resolve(res);
	}).catch(err =>{
		reject (err);
	})
})

var p2 = new Promie((resolve,reject) => {
this.$axios.get(httpUrl.getCompany).then(res => {
	resolve(res);
		}).catch(err =>{
			reject (err);
		})
})
//调用Promise.add().then(res => {})
	Promise.all([p1,p2]).then(res => {
		console.log(res);
	})
}
resolve(res);

vue不使用v-model的时候怎么监听数据变化
可以使用watch监听数据的变化

computed的实现原理
computed本质是一个惰性求值的观察者
computed内部实现了一个惰性的watcher, 也就是computed watcher, computed watcher不会立刻求值, 同时持有一个dep实例
其内部通过this.dirty属性标记计算属性是否需要重新求值
当computed的依赖状态发生改变时, 就会通知这个惰性的watcher
computed watcher通过this.dep.subs.length判断有没有订阅者
有的话, 会重新计算, 然后对比新旧值, 如果变化了, 会重新渲染( Vue想确保不仅仅是计算属性依赖的值发生变化, 而是当计算属性最终计算的值发生变化时才会触发渲染, watcher重新渲染, 本质上是一种优化。)
没有的话,仅仅把this.dirty = true ( 当计算属性依赖于气他数据时, 属性并不会立即重新计算, 只有之后其它地方需要读取属性的时候, 它才会真正计算, 即具备lazy( 懒计算 )特性)

computed和watch有什么区别及运用场景?
区别:
computed计算属性: 依赖其它属性值, 并且computed的值有缓存, 只有它依赖的属性值发生改变, 下一次获取computed的值时才会重新计算computed的值
watch侦听器: 更多的是[观察]的作用, 无缓存性, 类似于某些数据的监听回调, 每当监听的数据变化时都会执行回调进行后续操作
运用场景
的那个我们需要进行数值计算, 并且用使用computed依赖于其他数据时, 应, 因为可以利用computed的缓存性, 避免每次获取值时, 都要重新计算
当我们需要的数据变化时执行异步或开销较大的操作时, 应该使用watch, 使用watch选项允许我们执行异步操作( 访问一个API ),限制我们执行该操作的频率, 并在我们得到最终结果前, 设置中间状态。这些都是计算属性无法做到的

为什么在Vue3.0才用了Proxy,抛弃了object.definedProperty?
object.definedProperty本身有一定的监控到数组下标变化的能力, 但是在Vue中, 从性能/体验的性价比考虑, 尤大大就弃用了这个特性( Vue为什么不能检测数组变动 ) 为了解决这个问题, 经过vue内部处理后可以使用以下几种方法来监听数组
push/pop/shift/unshift/splice/sort/reverse
由于只针对了以上7种方法进行了hack处理, 所以其他数组的属性也是检测不到的, 还是具有一定的局限性.
Object.definedProperty只能劫持对象的属性,因此我么需要对每个对象的每个属性进行遍历。Vue2.0中通过递归+遍历data对象来实现对数据的监控, 如果属性值也是对象Proxy可以劫持整个对象, 并返回一个新的对象.Proxy不仅可以代理对象, 还可以代理数组。还可以代理动态增加的属性

Vue是如何对数组方法进行变异的?
Vue通过原型拦截的方式重写了数学的7个方法, 首先获取到这个数组的ob, 也就是它的Observer对象, 如果有新的值, 就调用observeArray对新的值进行监听, 然后手动调用notify, 通知render watcher 执行 update

Vue组件data为什么必须是函数?
因为组件是可以复用的, JS里对象是引用关系, 如果组件data是一个对象, 那么子组件中的data属性值会互相污染, 产生副作用。
所以一个组件的data选项必须是一个函数, 因此每个实例可以维护一份被返回对象的独立拷贝。new Vue的实例是不会被复用的,因此不存在以上问题。

说说Vue的渲染过程
调用complie函数, 生成render函数字符串, 编译过程如下:

  1. parse函数解析template生成ast(抽象语法树)
  2. optimize函数优化静态节点( 标记不需要每次都更新的内容, diff算法会直接跳过静态节点, 从而减少比较的过程, 优化了patch的过程)
    generate函数生成render函数字符串
  3. 调用new Watcher函数, 监听数据的变化, 当数据发生变化时, render函数执行生成vnode对象
  4. 调用patch方法, 对比新旧vnode对象, 通过DOM diff算法添加、修改、删除真正的DOM元素

Vue与Angular以及React的区别?
与AngularJS的区别:
相同点: 都支持指令: 内置指令和自定义指令; 都支持过滤器; 内置过滤器和自定义过滤器; 都支持双向数据绑定; 都不支持低端浏览器
不同点: AngularJS的学习成本高, 比如增加了Dependency Injection特性, 而Vue.js本身提供的API都比较简单、直观;在性能上, AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基本依赖追踪的观察并且使用异步队列更新, 所有的数据都是独立触发的
与React的区别:
相同点: React采用特殊的JSX语法, Vue.js 在组件开发中也推崇编写. vue特殊文本格式, 对文本内容都有一些约定, 两者都需要编译后使用; 中心思想相同: 一切都是组件, 组件实例之间可以嵌套; 都提供合理的钩子函数, 可以让开发者定制化地去处理需求; 都不内置列数Ajax/Route等功能到核心包, 而是以插件的方式加载; 在组件开发中都支持mixins的特性。
不同点:React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令, 过滤器等, 可以非常方便, 快捷地操作Virtual DOM.

生命周期相关面试题
生命总共分为8个阶段创建前/后、载入前/后、更新前/后、销毁前/后
创建前/后 :
在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。
在created阶段,vue实例的数据data有了,el还没有
载入前/后:
在beforeMount阶段,vue实例的$el和data都初始化了,单还没有挂载之前都是虚拟的demo阶段,data.message还未替换.
在mounted阶段,vue实例挂载完后,data.message成功渲染.
更新前/后:当data变化时,户触发beforeUpdate和update方法。
销毁前/后:
在执destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经结束了事件监听以及和dom的绑定,但是dom结构依然存在。

什么是vue生命周期?
vue实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM→渲染、更新→渲染、卸载等一系列过程,我们称这是Vue的生命周期。

vue生命周期的作用是什么?
生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程中更容易形成好的逻辑

vue生命周期总共有几个阶段?
总共可以分8个阶段:创建前/后、载入前/后、更新前/后、销毁前/后

第一次页面加载会触发哪几个钩子?
第一次页面加载时会触发beforeCreate、created、beforeMount、mounted这几个钩子

请列举出3个Vue常用的声明周期钩子函数
created:实例已经创建完成之后调用,在这一步,实例已经完成数据观测、属性和方法的运算,watch、event事件回调,然而,挂载阶段还没有开始, e l 属 性 目 前 还 不 可 见 m o u n t e d : e l 被 新 创 建 的 v m . el属性目前还不可见 mounted:el被新创建的vm. elmountedelvm.el替换,并挂载到实例上去之后调用该钩子,如果root实例挂在了一个文档内元素,当mounted被调用时vm.$el也在文档内。
activated:keep-alive组件激活时调用

DOM渲染在那个周期中已完成?
DOM渲染在mounted中就已经完成了

Vue-router

Vue-router的跳转原理:
vue-router实现单页面路由跳转,提供了三种方式: hash方式、history模式、abstract模式,根据mode参数来决定采用哪一种方式
● hash: 使用 URL hash 值来作路由。默认模式。
● history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。
● abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端
路由之间的跳转:
声明式(标签跳转):标签用于展示路由组件,DOM节点中使用v-link进行跳转,或使用router-link标签
编程式(js跳转)

怎么定义vue-router的动态路由以及如何获取传过来的动态参数?
在router目录下的index.js文件中,对path属性加上/:id
使用router对象的params id
Vue中,如何用watch去监听router变化
当路由发生变化的时候,在watch中写具体的业务逻辑

let vm = new Vue({
		el:"#app",
		data:{},
		router,
		watch:{
		$router(to,from){
			console.log(to.path);
		}
	}
})

vue-router有哪几种导航钩子?以及它的参数?
三种,一是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。
第二种:组件内的钩子
第三种:单独路由独享组件
beforeRouteEnter、afterEnter、beforeRouterUpdate、beforeRouteLeave
参数:有to(去的那个路由)、from(离开的路由)、next(一定要用这个函数才能去到下一个路由,如果不用就拦截)最常用就这几种
Vue的路由实现: hash模式和history模式(Vue的两种状态)

  1. hash——即地址栏URL的#符号,特点: 通过window.onhashchange的监听, 匹配不同的url路径,进行解析,加载不同的组件,然后动态的渲染出区域内的html内容,不会被包含在HTTP请求中,对后端完全没有影响
    HashHistory有两个方法:
    HashHistory.push()是将路由添加到浏览器访问历史的栈顶
    hashHistory.replace( ) 是替换掉当前栈顶的路由
    因为hash发生变化的url都会被浏览器历史访问栈记录下来,这样一来,尽管浏览器没有请求服务器,但是页面状态和url一一关联起来的,浏览器还是可以进行前进后退的

  2. history —— 利用了HTML5 History Interface中新增的pushState()和replaceState()方法。这两个方式应用于浏览器的历史记录栈,提供了对历史记录的修改功能。history模式不怕页面的前进和后腿,就怕刷新,当刷新时,如果服务器没有相应的响应或者资源,就会刷出404,而hash模式不会。

$ router和$route的区别
3. $ route从当前router跳转对象里面可以获取name、path、query、params等(传的参数有this.$ route.query或者this.$route.params接收)
4. $ router为VueRouter实例。想要导航到不同URL,则使用$ router.push方式,返回上一个history也是使用$ router.go方法

Vuex

Vuex是一个专门为vue.js应用设计的状态管理架构,统一管理和维护各个Vue组件的可变化状态(可以理解为Vue组件中的那些data)
Vuex的五大属性:

Vuex有五个核心概念:state、getters、mutations、actions、modules

//这五个核心概念问到的很多,还不知道友友们尽快去掌握一下。

state => 基本数据
getters => 从基本数据派生的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步
modules => 模块化Vuex

不用Vuex会带来什么问题?

  1. 可维性会下降,想修改数据要维护三个地方
    
  2. 可读性会下降,因为一个组件里的数据,根本就看不出来是从哪来的;
    
  3. 增加耦合,大量的上传派发,会让耦合性大大增加。而Vue用Component就是为了减少耦合
    

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;