vue面试题目
- vue数据
- 1. 说一下Vue的双向绑定数据的原理
- 2. 解释单向数据流和双向数据绑定
- 3. Vue 如何去除url中的
- 4. 对 MVC、MVVM的理解
- 5. 介绍虚拟DOM
- 6. vue生命周期的理解
- 7. 组件通信
- 8. vue-router 路由实现
- 9. v-if 和 v-show 区别
- 10. $ route和$router的区别
- 11. NextTick 是做什么的
- 12. Vue 组件 data 为什么必须是函数
- 13. 计算属性computed 和事件 methods 有什么区别
- 14. 对比 jQuery ,Vue 有什么不同
- Vue 中怎么自定义指令
- 16. Vue 中怎么自定义过滤器
- 17. 对 keep-alive 的了解
- 18. Vue 中 key 的作用
- 19. Vue 的核心是什么
- 20. vue 等单页面应用的优缺点
- 21. vue-router 使用params与query传参有什么区别
- MVVM的设计思想的优势?
- 事件传参
- 自定义指令:directive
- 计算属性:computed
- 监听器:watch
- 过滤器
- 组件
- 如何创建一个全局组件
- 如何创建一个局部组件
- 如何定义局部自定义指令
- 如何定义局部过滤器
- 组件传值
- 父向子传值
- 子向父传值
- 兄弟组件传值
- 跨组件传值
- 组件插槽
- Promise的使用
- axios拦截器
- 路由
- 在vue中路由传参
- 嵌套路由
- 编程式导航
- 路由钩子
- vue脚手架的安装和使用
- 命令行方式
- ui界面方式
- 安装element-ui
- 使用element中select组件
- 安装axios
- vue动画
- 利用类名添加动画
- 结合css动画库设置动画
- 加深面试题
- 虚拟dom
- Diff算法
- axios配置代理服务器
- 小程序分包加载
- v-if和v-for避免结合使用
- vue中route和router的区别
- 如何封装一个插件
- vueI18n的使用
- keep-alive
- 使用了本地存储为什么还使用vuex?
- js
- 重中之重、Vue的双向数据绑定原理是什么?
- 一、什么是MVVM?
- 二、mvvm和mvc区别?它和其它框架(jquery)的区别是什么?哪些场景适合?
- 三、vue的优点是什么?
- 四、 组件之间的传值?
- 五、路由之间跳转
- 六、vue.cli中怎样使用自定义的组件?有遇到过哪些问题吗?
- 七、vue如何实现按需加载配合webpack设置
- 八、vuex面试相关
- (1)vuex是什么?怎么使用?哪种功能场景使用它?
- (2)vuex有哪几种属性?
- (3)不用Vuex会带来什么问题?
- 九、 v-show和v-if指令的共同点和不同点
- 十、 如何让CSS只在当前组件中起作用
- 十一、的作用是什么?
- 十二、Vue中引入组件的步骤?
- 十三、指令v-el的作用是什么?
- 十四、在Vue中使用插件的步骤
- 十五、请列举出3个Vue中常用的生命周期钩子函数
- 十六、active-class是哪个组件的属性?
- 十七、怎么定义vue-router的动态路由以及如何获取传过来的动态参数?
- 十八、vue-router有哪几种导航钩子?
- 十九、生命周期相关面试题
- (1)、什么是vue生命周期
- (2)、vue生命周期的作用是什么
- (3)、vue生命周期总共有几个阶段
- (4)、第一次页面加载会触发哪几个钩子
- (5)、DOM 渲染在 哪个周期中就已经完成
- (6)、简单描述每个周期具体适合哪些场景
- 二十、说出至少4种vue当中的指令和它的用法?
- 二十一、vue-loader是什么?使用它的用途有哪些?
- 二十二、scss是什么?在vue.cli中的安装使用步骤是?有哪几大特性?
- 二十三、为什么使用key?
- 二十四、为什么避免 v-if 和 v-for 用在一起
- 二十五、VNode是什么?虚拟 DOM是什么?
- 二十六、vue-loader是什么?使用它的用途有哪些?
- 二十八、vue.cli中怎样使用自定义的组件?有遇到过哪些问题吗?
- 二十九、聊聊你对Vue.js的template编译的理解?
前言
作为前端开发中现行最火的框架之一,Vue 在面试中出现的频率不断增加。基于此,总结了一些 Vue 方面经常出现的面试题,留给自己查看消化,也分享给有需要的小伙伴
感兴趣的小伙伴也可以点击 这里,查看前端方面的其他面试题,欢迎 star 关注
如果文章中有出现纰漏、错误之处,还请看到的小伙伴留言指正,先行谢过
以下 ↓
vue数据
1. 说一下Vue的双向绑定数据的原理
vue
实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()
来劫持各个属性的 setter
,getter
,在数据变动时发布消息给订阅者,触发相应监听回调。
2. 解释单向数据流和双向数据绑定
单向数据流: 顾名思义,数据流是单向的。数据流动方向可以跟踪,流动单一,追查问题的时候可以更快捷。缺点就是写起来不太方便。例如:在父向子传值的时候,如果改变父组件的值,子组件会跟着同步更新,反之不允许
要使UI发生变更就必须创建各种 action
来维护对应的 state
双向数据绑定:数据之间是相通的,将数据变更的操作隐藏在框架内部。优点是在表单交互较多的场景下,会简化大量与业务无关的代码。缺点就是无法追踪局部状态的变化,增加了出错时 debug
的难度。例如:v-model
,数据发生变化,同步视图,视图发生变化,同步数据
3. Vue 如何去除url中的
vue-router
默认使用 hash 模式,所以在路由加载的时候,项目中的 url 会自带 #
。如果不想使用 #
, 可以使用 vue-router
的另一种模式 history
。
new Router({
mode: 'history',
routes: [ ]
})
需要注意的是,当我们启用 history
模式的时候,由于我们的项目是一个单页面应用,所以在路由跳转的时候,就会出现访问不到静态资源而出现 404 的情况,这时候就需要服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html
页面。
4. 对 MVC、MVVM的理解
MVC
特点:
View 传送指令到 Controller
Controller 完成业务逻辑后,要求 Model 改变状态
Model 将新的数据发送到 View,用户得到反馈
所有通信都是单向的
MVVM
特点:
各部分之间的通信,都是双向的
采用双向绑定:View 的变动,自动反映在 ViewModel,反之亦然
具体请移步 这里
5. 介绍虚拟DOM
6. vue生命周期的理解
vue实例有一个完整的生命周期,生命周期也就是指一个实例从开始创建到销毁的这个过程
生命周期:是指一个对象从创建到运行到销毁的整个过程,被称为生命周期
生命周期函数:在不同的生命周期阶段会自动执行对应的函数,而这些函数则被成为生命周期函数
// 创建阶段
beforeCreate() {
// 这个生命周函数,代表开始创建实例了
console.log('beforeCreate',this.num)
},
created () {
// 代表数据和方法已经初始化成功了,此处dom还没有挂载到页面上
console.log('created',this.num,this.$el)
},
beforeMount () {
// 挂在之前
console.log('beforeMount',this.$el)
},
mounted () {
// dom已经挂载了
console.log('mounted',this.$el)
},
// 运行更新阶段
beforeUpdate () {
// 数据更新,页面还没有同步
console.log('beforeUpdated',this.num,document.getElementById('app').innerHTML)
},
updated () {
// 数据更新,页面已经同步
console.log('updated',this.num,document.getElementById('app').innerHTML)
},
// 销毁阶段
beforeDestroy () {
// 销毁之前
console.log('beforeDestroy')
},
destroyed () {
// 已经销毁了
console.log('destroy')
}
beforeCreate() 在实例创建之间执行,数据未加载状态
created() 在实例创建、数据加载后,能初始化数据,dom渲染之前执行
beforeMount() 虚拟dom已创建完成,在数据渲染前最后一次更改数据
mounted() 页面、数据渲染完成,真实dom挂载完成
beforeUpadate() 重新渲染之前触发
updated() 数据已经更改完成,dom 也重新 render 完成,更改数据会陷入死循环
beforeDestory() 和 destoryed() 前者是销毁前执行(实例仍然完全可用),后者则是销毁后执行
7. 组件通信
1、父组件向子组件通信
子组件通过 props
属性,绑定父组件数据,实现双方通信
2、子组件向父组件通信
将父组件的事件在子组件中通过 $emit
触发
3、非父子组件、兄弟组件之间的数据传递
/*新建一个Vue实例作为中央事件总嫌*/
let event = new Vue();
/*监听事件*/
event.$on('eventName', (val) => {
//......do something
});
/*触发事件*/
event.$emit('eventName', 'this is a message.')
Vuex 数据管理,也可以通过vuex全局状态管理器去实现。
8. vue-router 路由实现
路由就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能
参考 这里
9. v-if 和 v-show 区别
使用了 v-if
的时候,如果值为 false
,那么页面将不会有这个 html
标签生成。v-show
则是不管值为 true
还是 false
,html
元素都会存在,只是 CSS 中的 display
显示或隐藏
10. $ route和$router的区别
$router
为 VueRoute
r 实例,想要导航到不同 URL,则使用 $router.push
方法$route
为当前 router
跳转对象里面可以获取 name
、 path
、 query
、 params
等
11. NextTick 是做什么的
$nextTick
是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick
,则可以在回调中获取更新后的 DOM
具体可参考官方文档 深入响应式原理
12. Vue 组件 data 为什么必须是函数
因为js本身的特性带来的,如果 data
是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有Vue实例的数据。如果将 data
作为一个函数返回一个对象,那么每一个实例的 data
属性都是独立的,不会相互影响了
13. 计算属性computed 和事件 methods 有什么区别
我们可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的
不同点:
computed
: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值对于 method
,只要发生重新渲染,method 调用总会执行该函数
14. 对比 jQuery ,Vue 有什么不同
jQuery
专注视图层,通过操作 DOM 去实现页面的一些逻辑渲染; Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发
Vue 中怎么自定义指令
全局注册
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
局部注册
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
参考 官方文档-自定义指令
16. Vue 中怎么自定义过滤器
可以用全局方法 Vue.filter()
注册一个自定义过滤器,它接收两个参数:过滤器 ID 和过滤器函数。过滤器函数以值为参数,返回转换后的值
Vue.filter('reverse', function (value) {
return value.split('').reverse().join('')
})
<!-- 'abc' => 'cba' -->
<span v-text="message | reverse"></span>
过滤器也同样接受全局注册和局部注册
17. 对 keep-alive 的了解
keep-alive
是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
<keep-alive>
<component>
<!-- 该组件将被缓存! -->
</component>
</keep-alive>
可以使用API提供的props
,实现组件的动态缓存
具体参考 官方API 也可移步 这里
18. Vue 中 key 的作用
key
的特殊属性主要用在 Vue 的虚拟 DOM
算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key
,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key
,它会基于 key
的变化重新排列元素顺序,并且会移除 key
不存在的元素。有相同父元素的子元素必须有独特的 key
。重复的 key
会造成渲染错误
具体参考 官方API
19. Vue 的核心是什么
数据驱动 组件系统
20. vue 等单页面应用的优缺点
优点:
- 良好的交互体验
- 良好的前后端工作分离模式
- 减轻服务器压力
缺点:
- SEO难度较高
- 前进、后退管理
- 初次加载耗时多
21. vue-router 使用params与query传参有什么区别
vue-router
可以通过 params
与 query
进行传参
// 传递
this.$router.push({path: './xxx', params: {xx:xxx}})
this.$router.push({path: './xxx', query: {xx:xxx}})
// 接收
this.$route.params
this.$route.query
- params 是路由的一部分,必须要有。query 是拼接在 url 后面的参数,没有也没关系
- params 不设置的时候,刷新页面或者返回参数会丢,query 则不会有这个问题
MVVM的设计思想的优势?
双向绑定技术,当Model变化时,View也会自动变化,view发生更新,model也跟着同步
我们减少了dom的操作,因为我们只需要关注数据就可以
mvvm的设计思想大大提高了代码的耦合性
事件传参
没有传递参数,事件函数的默认第一个参数是事件对象
如果传递了参数,事件函数就没有了默认参数,全部变为对应位置的实参的形参,就没有了事件对象
既有自己传的的参数,也有事件对象,通过$event传递事件对象,在事件函数内部通过通过对应位置的形参来接收事件对象,传递刀了event没有强制性的位置,但是建议放在最后
自定义指令:directive
为什么自定义指令?
vue提供的系统指令满足不了我们的需求,那么我们就需要自定义指令
通过Vue.directive进行自定义指令的定义
-
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 -
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
计算属性:computed
定义的时候是一个方法,使用的时候当作属性使用
只要return后面的数据发生变化,该计算属性就会重新计算
计算属性具有缓存功能
监听器:watch
watch侦听器如果监听的是一个对象,需要开启深度监听
watch:{
num:{
// 监听数据发生变化的处理函数
handler(newNum) {
console.log(newNum)
},
// 是否开启深度监听
deep: true
}
}
过滤器
<div id="app">
<div>
{{ date | formatDate('-')}}
</div>
<div>
{{ date | formatDate('/')}}
</div>
</div>
<script src="vue.js"></script>
<script>
Vue.filter('formatDate',(data,line)=>{
console.log(data,line)
const y = data.getFullYear()
const m = (data.getMonth()+1).toString().padStart(2,0)
const d = data.getDate().toString().padStart(2,0)
return y+line+m+line+d
})
new Vue({
el: '#app',
data:{
date: new Date
}
})
</script>
组件
如何创建一个全局组件
通过Vue.component来创建一个全局组件,第一个参数是组件名字,第二个参数是组件的配置对象,可以通过template配置组件的结构,data定义数据等等
为什么组件内部的data是一个函数?
为了保证组件在每次使用的时候保证数据是当前组件唯一的,互不影响
如何创建一个局部组件
在组件内部通过components来创建一个局部组件
全局组件和局部组件的区别
局部组件:只能在当前的父组件中使用
全局组件: 在任意地方使用
如何定义局部自定义指令
在组件内部通过directives来创建一个局部指令
全局指令和局部指令的区别
局部指令:只能在当前的组件中使用
全局指令: 在任意地方使用
如何定义局部过滤器
在组件内部通过filters来创建一个局部过滤器
全局过滤器和局部过滤器的区别
局部过滤器:只能在当前的组件中使用
全局过滤器: 在任意地方使用
组件传值
父向子传值
- 父亲怎么传:通过属性绑定形式传
- 儿子怎么接收:通过props来接收
子向父传值
- 子怎么传:通过this.$emit触发一个自定义事件,并且发送一个值
- 父怎么接收:通过定义自定义事件的事件函数的形参来接收
兄弟组件传值
定义一个事件中心,或者是第三方
接收值的组件:通过该事件中心的$on来定义自定义事件的事件函数来接收值
eventBus.$on('getTab1',(data)=>{
console.log('接收tab1传递的值',data)
})
另一个兄弟组件怎么传:通过事件中心的$emit触发对应的 刀了 on的事件,并且把值传递过去
eventBus.$emit('getTab1',this.num)
跨组件传值
Vue.component('my-sub1',{
template:'#my-sub1',
data() {
return {
money: 10000000
}
},
provide:{
money:1000
},
components:{
'sub-a':{
template:'<div>子组件<sub-b></sub-b></div>',
components:{
'sub-b':{
template:'<div>子组件{{money}}</div>',
inject:['money']
}
}
}
}
})
new Vue({
el: '#app'
})
组件插槽
- 默认插槽:
- 在组件标签中间可以传递一些子节点
- 组件内部利用slot标签进行接收
- 具名插槽
- 在组件标签中间通过定义slot的名字传递子节点
<my-banner>
<div slot="header">
头部
</div>
<div slot="footer">
底部
</div>
</my-banner>
- 组件内部利用slot的name进行对应接收
<template id="banner">
<div>
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>
- 作用域插槽
- 在组件内部定义数据,将数据传递给插槽的结构
- 通过给slot动态绑定属性
<template id="my-li">
<ul>
<li v-for="item in arr">
<slot :row="item"></slot>
</li>
</ul>
</template>
插槽内部:通过slot-scope=“scope”来接收
<my-li>
<template slot-scope="scope">
<p>{{scope.row}}</p>
</template>
</my-li>
<my-li>
<template slot-scope="scope">
<a href="04-侦听器.html">{{scope.row}}</a>
</template>
</my-li>
Promise的使用
利用Promise处理异步解决回调地狱的问题
Promise的all的方法
Promise的race的方法
面试题:
现在有三个接口地址,需要三个接口地址请求完事之后进行下一步的逻辑处理(不一定按顺序请求完成)
// .then回调
axios.get('http://xxx').then(res=>{
console.log(res)
axios.get('http://xxx').then(res=>{
console.log(res)
axios.get('http://xxx').then(res=>{
console.log(res)
})
})
})
// .then返回新的Promise继续调用.then
axios.get('xxx').then(res=>{
return axios.get('xxx')
}).then(res=>{
return axios.get('xxx')
}).then(res=>{
console.log('三个请求完事')
})
// async await
const asyncHandle = async function(){
const res1 = await axios.get('http://xxx1')
const res2 = await axios.get('http://xxx2')
const res3 = await axios.get('http://xxx3')
console.log(res1,res2,res3)
}
asyncHandle()
// Promise all方法
const getComments = new Promise(()=>{
axios.get('http://xxx')
})
Promise.all([
axios.get('http://xxx1'),
axios.get('http://xxx2'),
axios.get('http://xxx3')
]).then(res=>{
console.log(res)
})
axios拦截器
请求拦截
axios.interceptors.request.use
响应拦截
axios.interceptors.response.use
路由
什么是路由?
路由就是对应关系,组件和url地址,根据不同的地址显示不同的组件,路由也是实现spa(单页面应用程序)的主要核心,因为单页面应用程序,就是只有一个html,在这个html里面切换组件,根据url,例如地址为/home,在这个页面中就显示home组件
前端路由:url和组件
后端路由:根据不同的地址请求不同的接口
在vue中路由传参
- params传参
在跳转的时候可以通过/home/10
路由规则:
new VueRouter({
routes: [
{
path: '/home/:id',
component: Home
}
]
})
组件内部怎么来接收参数
this.$route.params.id
- query传参
在跳转的时候可以通过/home?id=10
组件内部怎么来接收参数
this.$route.query.id
路由history
模式注意的问题
嵌套路由
编程式导航
路由钩子
全局钩子:都会对所有的路由进行拦截
beforeEach
:进入之前
afterEach:
已经进入了
路由独享钩子:可以针对某一个路由进行拦截,这个需要写在路由规则里
{
path: '/',
name: 'home',
beforeEnter: (to,from,next)=>{
console.log('即将进入home')
},
component: Home
}
组件内的守卫:
针对组件进行拦截
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
next()
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
console.log('即将离开about')
if(confirm('当前表单没有提交?确定要离开首页?')){
next()
}
}
vue脚手架的安装和使用
命令行方式
利用vue.config.js关闭esLint
ui界面方式
安装element-ui
安装vue-cli-plugin-element插件
使用element中select组件
注册组件
import Vue from 'vue'
import { Button, Select } from 'element-ui'
Vue.use(Button)
Vue.use(Select)
使用组件
<el-select v-model="value" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
复制数据
options: [{
value: '选项1',
label: '黄金糕'
}, {
value: '选项2',
label: '双皮奶'
}, {
value: '选项3',
label: '蚵仔煎'
}, {
value: '选项4',
label: '龙须面'
}, {
value: '选项5',
label: '北京烤鸭'
}],
value: ''
安装axios
安装依赖》运行依赖》axios
使用axios发送请求
利用Vue.prorotype挂载到原型上
vue动画
利用类名添加动画
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。v-enter-to
: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。v-leave
: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。v-leave-to
: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
首先给需要添加动画的元素用transition包裹起来
利用类名实现动画
.v-enter,.v-leave-to{
transform: translate(200px,0)
}
.v-enter-active,.v-leave-active{
transition: transform 1s;
}
.v-enter-to,.v-leave{
transform: translate(0,0)
}
自定义动画类名
<transition name="box2">
<div class="box" v-show="flag">
</div>
</transition>
.box2-enter,.box2-leave-to{
opacity: 0;
}
.box2-enter-active,.box2-leave-active{
transition: opacity 1s;
}
.box2-enter-to,.box2-leave{
opacity: 1;
}
结合css动画库设置动画
引入css动画库
通过enter-active-class
、leave-active-class
设置动画
<transition
enter-active-class="animated bounceIn"
leave-active-class="animated bounceOut"
>
<div class="box" v-show="flag">
</div>
</transition>
加深面试题
虚拟dom
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容
Diff算法
虚拟dom高效更新执行过程
- 初次渲染时,会根据model数据创建一个虚拟DOM对象(树)
- 根据虚拟DOM生成真正的DOM,渲染到页面
- 当数据变化后,会重新根据新的数据,创建新的虚拟DOM对象(树)
- 与上一次得到的虚拟DOM对象,使用Diff算法比对(找不同),得到需要更新的内容
- 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面
- 什么是虚拟dom:用js对象来表示页面上dom元素的的样式体现
- 虚拟dom的作用:高效更新页面,还有就是让react脱离了浏览器的概念
- 怎么来高效更新页面的:就是在第一次渲染页面的时候生成一份初始的虚拟dom树,然后当数据改变之后,再生成一份虚拟dom树,然后根据新旧dom树进行对比,对比完成之后,把有区别的进行更新
- diff算法的作用就是:新旧虚拟dom树在对比的时候就是通过diff算法来对比的
- diff算法又是怎么去对比的:tree diff、component diff、element diff
面试语术:
你知道虚拟dom吗?简单又谈一下?
虚拟dom可以实现高效更新,(后面如果自己能说一气说出来)
如何实现高效更新的?
利用新旧虚拟dom树进行对比,从而进行局部进行更新
如何进行新旧dom树进行对比?
利用diff算法,主要是tree diff树对比,component diff 组件对比,element diff 元素对比
加上一些其他话术
所以虚拟dom在前端中不管是vue、react等等都采用
axios配置代理服务器
什么是跨域?
在浏览器里面域名、端口、ip地址、协议,有任何一项不同,则跨域
A网站:http://localhost:8080/#/
B网站:http://localhost:3000/#/
处理跨域的方式?
JsonP(只能处理get请求)、cors(后端开启)、代理服务器
module.exports = {
devServer: {
host: 'localhost',
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',// 要跨域的域名
changeOrigin: true, // 是否开启跨域
},
'/get': {
target: 'http://localhost:3000',// 要跨域的域名
changeOrigin: true, // 是否开启跨域
}
}
}
}
小程序分包加载
某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。
在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。
目前小程序分包大小有以下限制:
整个小程序所有分包大小不超过 12M
单个分包/主包大小不能超过 2M
对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。
代码演示,分包加载之前
pages: [
'pages/tabs/home',
'pages/tabs/cates',
'pages/tabs/search',
'pages/tabs/cart',
'pages/tabs/my',
'pages/goods_list',
'pages/goods_detail/main',
'pages/order',
'pages/orderList'
]
抽离之后
pages: [
'pages/tabs/home',
'pages/tabs/cates',
'pages/tabs/search',
'pages/tabs/cart',
'pages/tabs/my',
'pages/order',
'pages/orderList'
],
subpackages: [
{
name: 'goods',
root: 'goods',
pages: [
'goods_list',
'goods_detail/main'
]
}
]
图片或者图标等资源文件使用线上资源
v-if和v-for避免结合使用
因为v-for的优先级大于v-if,所以会先根据所有数据生成结构,然后在根据,v-if进行按需渲染
<div id="app">
<ul>
<li v-for="item in arr" v-if="item%2===0">{{ item }}</li>
</ul>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
arr: [
1,2,3,4
]
}
})
</script>
我们可以采用计算属性来避免此问题,这样的话,先通过计算属性,计算出要渲染的数据,然后直接循环计算属性即可
<div id="app">
<ul>
<li v-for="item in newArr">{{ item }}</li>
</ul>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
arr: [
1,2,3,4
]
},
computed: {
newArr() {
return this.arr.filter(item=>{
return item%2===0
})
}
}
})
</script>
vue中route和router的区别
route是当前路由信息,可以获取到当前路由地址参数等等
router是全局路由实例对象,可以通过router进行路由的跳转后退等等
如何封装一个插件
定义一个button组件,在index.js中引入并注册
在components中创建一个index.js
import sgButton from './button.vue'
function install (Vue) {
Vue.component(sgButton.name, sgButton)
}
export default {
install
}
在main.js中导入该js文件,当调用Vue.use方法,会默认调用内部的install方法
面试题
你有封装过插件吗? Vue.use的原理是什么?
当调用Vue.use方法,会默认调用内部的install方法,install这个方法默认的第一个形参就是Vue,这样的话我们就可以通过Vue注册一些全局组件,给Vue扩展一下方法。
vueI18n的使用
vue中实现语言切换的方式如何实现的
1.NPM 项目安装
cnpm i vue-i18n
2.使用方法
/* 国际化使用规则 */
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
<!-- 需要国际化的数据定义在此处 -->
const messages = {
en: {
message: {
hello: 'world hello'
}
},
zh: {
message: {
hello: '世界'
}
}
}
<!-- 使用i18n -->
const i18n = new VueI18n({
locale: 'zh',
messages
})
export default {
data () {
return {
hello: this.$t('message.hello')
}
},
i18n
}
3.页面数据使用
<div id="#app">
<p>{{ $t("message.hello") }}</p>
</div>
1
2
3
4.案例练习
定义基本结构
<div id="app">
<button>切换语言</button>
<ul>
<li>首页</li>
<li>新闻</li>
<li>关于</li>
<li>概况</li>
</ul>
</div>
ul{
list-style: none;
li{
width: 20%;
height: 70px;
line-height: 70px;
background: green;
color: #fff;
float: left;
margin-left: 2%;
text-align: center;
line-height: 70px;
}
}
定义语言包
zh.js
export default {
nav: ['首页', '新闻', '概况', '关于']
}
en.js
export default {
nav: ['home', 'news', 'gk', 'about']
}
在main.js中引入语言包
import zh from './i18n/zh.js'
import en from './i18n/en.js'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'en',
messages: {
zh,
en
}
})
数据渲染和切换
<template>
<div id="app">
<button @click="changeLang">切换语言</button>
<ul>
<li v-for="(item, index) in $t('nav')" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
methods: {
changeLang () {
this.$i18n.locale = this.$i18n.locale === 'en' ? 'zh' : 'en'
}
}
}
</script>
keep-alive
使用了本地存储为什么还使用vuex?
vuex的数据是响应式的,而本地存储的数据不是响应式的,使用本地存储是为了让数据持久化
js
js静态成员和实例成员
静态成员:静态成员 在构造函数本身上添加的成员 如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问
实例成员:实例成员就是构造函数内部通过this添加的成员 如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问
重中之重、Vue的双向数据绑定原理是什么?
答:vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
一、什么是MVVM?
MVVM是Model-View-ViewModel的缩写。MVVM是一种设计思想。Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
二、mvvm和mvc区别?它和其它框架(jquery)的区别是什么?哪些场景适合?
mvc和mvvm其实区别并不大。都是一种设计思想。主要就是mvc中Controller演变成mvvm中的viewModel。mvvm主要解决了mvc中大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
区别:vue数据驱动,通过数据来显示视图层而不是节点操作。
场景:数据操作比较多的场景,更加便捷
三、vue的优点是什么?
低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
四、 组件之间的传值?
父组件与子组件传值
父组件通过标签上面定义传值
子组件通过props方法接受数据
子组件向父组件传递数据
子组件通过$emit方法传递参数
五、路由之间跳转
声明式(标签跳转) 编程式( js跳转)
六、vue.cli中怎样使用自定义的组件?有遇到过哪些问题吗?
第一步:在components目录新建你的组件文件(indexPage.vue),script一定要export default {}
第二步:在需要用的页面(组件)中导入:import indexPage from ‘@/components/indexPage.vue’
第三步:注入到vue的子组件的components属性上面,components:{indexPage}
第四步:在template视图view中使用,
例如有indexPage命名,使用的时候则index-page
七、vue如何实现按需加载配合webpack设置
webpack中提供了require.ensure()来实现按需加载。以前引入路由是通过import 这样的方式引入,改为const定义的方式进行引入。
不进行页面按需加载引入方式:import home from ‘…/…/common/home.vue’
进行页面按需加载的引入方式:
const home = r => require.ensure( [], () => r (require(’…/…/common/home.vue’)))
八、vuex面试相关
(1)vuex是什么?怎么使用?哪种功能场景使用它?
vue框架中状态管理。在main.js引入store,注入。新建一个目录store,…… export 。场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车
(2)vuex有哪几种属性?
有五种,分别是 State、 Getter、Mutation 、Action、 Module
vuex的State特性
A、Vuex就是一个仓库,仓库里面放了很多对象。其中state就是数据源存放地,对应于一般Vue对象里面的data
B、state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据发生改变,依赖这个数据的组件也会发生更新
C、它通过mapState把全局的 state 和 getters 映射到当前组件的 computed 计算属性中
vuex的Getter特性
A、getters 可以对State进行计算操作,它就是Store的计算属性
B、 虽然在组件内也可以做计算属性,但是getters 可以在多组件之间复用
C、 如果一个状态只在一个组件内使用,是可以不用getters
vuex的Mutation特性
Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。
(3)不用Vuex会带来什么问题?
可维护性会下降,想修改数据要维护三个地方;
可读性会下降,因为一个组件里的数据,根本就看不出来是从哪来的;
增加耦合,大量的上传派发,会让耦合性大大增加,本来Vue用Component就是为了减少耦合,现在这么用,和组件化的初衷相背。
九、 v-show和v-if指令的共同点和不同点
v-show指令是通过修改元素的display的CSS属性让其显示或者隐藏
v-if指令是直接销毁和重建DOM达到让元素显示和隐藏的效果
十、 如何让CSS只在当前组件中起作用
将当前组件的
十一、的作用是什么?
包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。
十二、Vue中引入组件的步骤?
1)采用ES6的import … from …语法或CommonJS的require()方法引入组件
2)对组件进行注册,代码如下
// 注册
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
3)使用组件
十三、指令v-el的作用是什么?
提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标.可以是 CSS 选择器,也可以是一个 HTMLElement 实例
十四、在Vue中使用插件的步骤
采用ES6的import … from …语法或CommonJSd的require()方法引入插件
使用全局方法Vue.use( plugin )使用插件,可以传入一个选项对象Vue.use(MyPlugin, { someOption: true })
十五、请列举出3个Vue中常用的生命周期钩子函数
created: 实例已经创建完成之后调用,在这一步,实例已经完成数据观测, 属性和方法的运算, watch/event事件回调. 然而, 挂载阶段还没有开始, e l 属 性 目 前 还 不 可 见 m o u n t e d : e l 被 新 创 建 的 v m . el属性目前还不可见 mounted : el被新创建的 vm.el属性目前还不可见mounted:el被新创建的vm.el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
activated: keep-alive组件激活时调用
十六、active-class是哪个组件的属性?
vue-router模块的router-link组件。
十七、怎么定义vue-router的动态路由以及如何获取传过来的动态参数?
在router目录下的index.js文件中,对path属性加上/:id。
使用router对象的params.id。
十八、vue-router有哪几种导航钩子?
三种,一种是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。
第二种:组件内的钩子;
第三种:单独路由独享组件
十九、生命周期相关面试题
总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
创建前/后: 在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el还没有。
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后:当data变化时,会触发beforeUpdate和updated方法。
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
(1)、什么是vue生命周期
答: Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
(2)、vue生命周期的作用是什么
答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
(3)、vue生命周期总共有几个阶段
答:可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后
(4)、第一次页面加载会触发哪几个钩子
答:第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
(5)、DOM 渲染在 哪个周期中就已经完成
答:DOM 渲染在 mounted 中就已经完成了。
(6)、简单描述每个周期具体适合哪些场景
答:生命周期钩子的一些使用方法:
beforecreate : 可以在这加个loading事件,在加载实例时触发
created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用
mounted : 挂载元素,获取到DOM节点
updated : 如果对数据统一处理,在这里写上相应函数
beforeDestroy : 可以做一个确认停止事件的确认框
nextTick : 更新数据后立即操作dom
二十、说出至少4种vue当中的指令和它的用法?
v-if:判断是否隐藏;v-for:数据循环;v-bind:class:绑定一个属性;v-model:实现双向绑定
二十一、vue-loader是什么?使用它的用途有哪些?
解析.vue文件的一个加载器。
用途:js可以写es6、style样式可以scss或less、template可以加jade等
二十二、scss是什么?在vue.cli中的安装使用步骤是?有哪几大特性?
答:css的预编译。
使用步骤:
第一步:先装css-loader、node-loader、sass-loader等加载器模块
第二步:在build目录找到webpack.base.config.js,在那个extends属性中加一个拓展.scss
第三步:在同一个文件,配置一个module属性
第四步:然后在组件的style标签加上lang属性 ,例如:lang=”scss”
特性:
可以用变量,例如($变量名称=值);
可以用混合器,例如()
可以嵌套
二十三、为什么使用key?
当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。
二十四、为什么避免 v-if 和 v-for 用在一起
当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,通过v-if 移动到容器元素,不会再重复遍历列表中的每个值。取而代之的是,我们只检查它一次,且不会在 v-if 为否的时候运算 v-for。
二十五、VNode是什么?虚拟 DOM是什么?
Vue在 页面上渲染的节点,及其子节点称为“虚拟节点 (Virtual Node)”,简写为“VNode”。“虚拟 DOM”是由 Vue 组件树建立起来的整个 VNode 树的称呼。
二十六、vue-loader是什么?使用它的用途有哪些?
答:解析.vue文件的一个加载器,跟template/js/style转换成js模块。
用途:js可以写es6、style样式可以scss或less、template可以加jade等
二十七、请说出vue.cli项目中src目录每个文件夹和文件的用法?
答:assets文件夹是放静态资源;components是放组件;router是定义路由相关的配置;view视图;app.vue是一个应用主组件;main.js是入口文件
二十八、vue.cli中怎样使用自定义的组件?有遇到过哪些问题吗?
答:
第一步:在components目录新建你的组件文件(smithButton.vue),script一定要export default {
第二步:在需要用的页面(组件)中导入:import smithButton from ‘…/components/smithButton.vue’
第三步:注入到vue的子组件的components属性上面,components:{smithButton}
第四步:在template视图view中使用,
问题有:smithButton命名,使用的时候则smith-button。
二十九、聊聊你对Vue.js的template编译的理解?
答:简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点)
详情步骤:
首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创建编译器的。另外compile还负责合并option。
然后,AST会经过generate(将AST语法树转化成render funtion字符串的过程)得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)