完整版在线阅读 http://interview.poetries.top
1 对于MVVM的理解
MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。
MVVM 是 Model-View-ViewModel 的缩写
Model: 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。我们可以把Model称为数据层,因为它仅仅关注数据本身,不关心任何行为
View: 用户操作界面。当ViewModel对Model进行更新的时候,会通过数据绑定更新到View
ViewModel:业务逻辑层,View需要什么数据,ViewModel要提供这个数据;View有某些操作,ViewModel就要响应这些操作,所以可以说它是Model for View.
总结:MVVM模式简化了界面与业务的依赖,解决了数据频繁更新。MVVM 在使用当中,利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化。
2 请详细说下你对vue生命周期的理解
答:总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后
生命周期是什么
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是Vue的生命周期
各个生命周期的作用
生命周期 |
描述 |
beforeCreate |
组件实例被创建之初,组件的属性生效之前 |
created |
组件实例已经完全创建,属性也绑定,但真实dom还没有生成,$el还不可用 |
beforeMount |
在挂载开始之前被调用:相关的 render 函数首次被调用 |
mounted |
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 |
beforeUpdate |
组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
update |
组件数据更新之后 |
activited |
keep-alive专属,组件被激活时调用 |
deadctivated |
keep-alive专属,组件被销毁时调用 |
beforeDestory |
组件销毁前调用 |
destoryed |
组件销毁后调用 |
image
由于Vue会在初始化实例时对属性执行getter/setter
转化,所以属性必须在data
对象上存在才能让Vue
将它转换为响应式的。Vue提供了$set
方法用来触发视图更新
export default {
data(){
return {
obj: {
name: 'fei'
}
}
},
mounted(){
this.$set(this.obj, 'sex', 'man')
}
}
什么是vue生命周期?
vue生命周期的作用是什么?
vue生命周期总共有几个阶段?
第一次页面加载会触发哪几个钩子?
DOM 渲染在哪个周期中就已经完成?
3 Vue实现数据双向绑定的原理:Object.defineProperty()
vue
实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript
对象传给 Vue 实例来作为它的 data
选项时,Vue 将遍历它的属性,用 Object.defineProperty()
将它们转为 getter/setter
。用户看不到 getter/setter
,但是在内部它们让 Vue
追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM
作为数据绑定的入口,整合Observer
,Compile
和Watcher
三者,通过Observer
来监听自己的model
的数据变化,通过Compile
来解析编译模板指令(vue
中是用来解析 {{}}
),最终利用watcher
搭起observer
和Compile
之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input
)—>数据model
变更双向绑定效果。
4 Vue组件间的参数传递
父组件与子组件传值
父组件传给子组件:子组件通过props
方法接受数据;
非父子组件间的数据传递,兄弟组件传值
eventBus
,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适(虽然也有不少人推荐直接用VUEX
,具体来说看需求)
5 Vue的路由实现:hash模式 和 history模式
hash
模式:在浏览器中符号“#”
,#以及#后面的字符称之为hash
,用 window.location.hash
读取。特点:hash
虽然在URL
中,但不被包括在HTTP
请求中;用来指导浏览器动作,对服务端安全无用,hash
不会重加载页面。
history
模式:history
采用HTML5
的新特性;且提供了两个新方法:pushState()
, replaceState()
可以对浏览器历史记录栈进行修改,以及popState
事件的监听到状态变更
5 vue路由的钩子函数
首页可以控制导航跳转,beforeEach
,afterEach
等,一般用于页面title
的修改。一些需要登录才能调整页面的重定向功能。
6 vuex是什么?怎么使用?哪种功能场景使用它?
只用来读取的状态集中放在store
中;改变状态的方式是提交mutations
,这是个同步的事物;异步逻辑应该封装在action
中。
在main.js
引入store
,注入。新建了一个目录store
,… export
场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车
image
state
:Vuex
使用单一状态树,即每个应用将仅仅包含一个store
实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
mutations
:mutations
定义的方法动态修改Vuex
的 store
中的状态或数据
getters
:类似vue
的计算属性,主要用来过滤一些数据。
action
:actions
可以理解为通过将mutations
里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view
层通过 store.dispath
来分发 action
image
modules
:项目特别复杂的时候,可以让每一个模块拥有自己的state
、mutation
、action
、getters
,使得结构非常清晰,方便管理
image
7 v-if 和 v-show 区别
8 $route
和$router
的区别
9 如何让CSS只在当前组件中起作用?
将当前组件的<style>
修改为<style scoped>
10 <keep-alive></keep-alive>
的作用是什么?
keep-alive可以实现组件缓存,当组件切换时不会对当前组件进行卸载
比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用<keep-alive></keep-alive>
进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染
11 指令v-el的作用是什么?
提供一个在页面上已存在的 DOM
元素作为 Vue
实例的挂载目标.可以是 CSS 选择器,也可以是一个 HTMLElement
实例,
12 在Vue中使用插件的步骤
采用ES6
的import ... from ...
语法或CommonJS
的require()
方法引入插件
使用全局方法Vue.use( plugin )
使用插件,可以传入一个选项对象Vue.use(MyPlugin, { someOption: true })
13 请列举出3个Vue中常用的生命周期钩子函数?
created
: 实例已经创建完成之后调用,在这一步,实例已经完成数据观测, 属性和方法的运算, watch/event
事件回调. 然而, 挂载阶段还没有开始, $el
属性目前还不可见
mounted
: el
被新创建的 vm.$el
替换,并挂载到实例上去之后调用该钩子。如果 root
实例挂载了一个文档内元素,当 mounted
被调用时 vm.$el
也在文档内。
activated
: keep-alive
组件激活时调用
14 vue-cli 工程技术集合介绍
问题一:构建的 vue-cli 工程都到了哪些技术,它们的作用分别是什么?
vue.js
:vue-cli
工程的核心,主要特点是 双向数据绑定 和 组件系统。
vue-router
:vue
官方推荐使用的路由框架。
vuex
:专为 Vue.js
应用项目开发的状态管理器,主要用于维护vue
组件间共用的一些 变量 和 方法。
axios
( 或者 fetch
、ajax
):用于发起 GET
、或 POST
等 http
请求,基于 Promise
设计。
vuex
等:一个专为vue
设计的移动端UI组件库。
创建一个emit.js
文件,用于vue
事件机制的管理。
webpack
:模块加载和vue-cli
工程打包器。
问题二:vue-cli 工程常用的 npm 命令有哪些?
npm install
npm run dev
npm run build
npm run build --report
在浏览器上自动弹出一个 展示 vue-cli
工程打包后 app.js
、manifest.js
、vendor.js
文件里面所包含代码的页面。可以具此优化 vue-cli
生产环境部署的静态资源,提升 页面 的加载速度
15 NextTick
nextTick
可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM
16 vue的优点是什么?
低耦合。视图(View
)可以独立于Model
变化和修改,一个ViewModel
可以绑定到不同的"View"
上,当View变化的时候Model可以不变,当Model
变化的时候View
也可以不变
可重用性。你可以把一些视图逻辑放在一个ViewModel
里面,让很多view
重用这段视图逻辑
可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel
来写
17 路由之间跳转?
声明式(标签跳转)
<router-link :to="index">
编程式( js跳转)
router.push('index')
18 实现 Vue SSR
其基本实现原理
app.js
作为客户端与服务端的公用入口,导出 Vue
根实例,供客户端 entry
与服务端 entry
使用。客户端 entry
主要作用挂载到 DOM
上,服务端 entry
除了创建和返回实例,还进行路由匹配与数据预获取。
webpack
为客服端打包一个 Client Bundle
,为服务端打包一个 Server Bundle
。
服务器接收请求时,会根据 url
,加载相应组件,获取和解析异步数据,创建一个读取 Server Bundle
的 BundleRenderer
,然后生成 html
发送给客户端。
客户端混合,客户端收到从服务端传来的 DOM
与自己的生成的 DOM 进行对比,把不相同的 DOM
激活,使其可以能够响应后续变化,这个过程称为客户端激活 。为确保混合成功,客户端与服务器端需要共享同一套数据。在服务端,可以在渲染之前获取数据,填充到 stroe
里,这样,在客户端挂载到 DOM
之前,可以直接从 store
里取数据。首屏的动态数据通过 window.__INITIAL_STATE__
发送到客户端
Vue SSR
的实现,主要就是把 Vue
的组件输出成一个完整 HTML
, vue-server-renderer
就是干这事的
19 Vue 组件 data 为什么必须是函数
20 Vue computed 实现
实现时,主要如下
初始化 data
, 使用 Object.defineProperty
把这些属性全部转为 getter/setter
。
初始化 computed
, 遍历 computed
里的每个属性,每个 computed
属性都是一个 watch
实例。每个属性提供的函数作为属性的 getter
,使用 Object.defineProperty
转化。
Object.defineProperty getter
依赖收集。用于依赖发生变化时,触发属性重新计算。
若出现当前 computed
计算属性嵌套其他 computed
计算属性时,先进行其他的依赖收集
21 Vue complier 实现
可以简单理解成以下步骤:
parse
过程,将 template
利用正则转化成AST
抽象语法树。
optimize
过程,标记静态节点,后 diff
过程跳过静态节点,提升性能。
generate
过程,生成 render
字符串
22 怎么快速定位哪个组件出现性能问题
用 timeline
工具。大意是通过 timeline
来查看每个函数的调用时常,定位出哪个函数的问题,从而能判断哪个组件出了问题
23 开发中常用的指令有哪些
使用了v-if的时候,如果值为false,那么页面将不会有这个html标签生成。v-show则是不管值为true还是false,html元素都会存在,只是CSS中的display显示或隐藏
语法:v-bind:title="msg"
简写::title="msg"
24 Proxy 相比于 defineProperty 的优势
Object.defineProperty() 的问题主要有三个:
不能监听数组的变化
必须遍历对象的每个属性
必须深层遍历嵌套的对象
Proxy 在 ES2015 规范中被正式加入,它有以下几个特点
除了上述两点之外,Proxy 还拥有以下优势:
25 vue-router 有哪几种导航守卫?
全局守卫
vue-router全局有三个守卫
router.beforeEach
全局前置守卫 进入路由之前
router.beforeResolve
全局解析守卫(2.5.0+) 在beforeRouteEnter
调用之后调用
router.afterEach
全局后置钩子 进入路由之后
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
路由独享守卫
如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
// ...
}
}
]
})
路由组件内的守卫
beforeRouteEnter 进入路由前, 在路由独享守卫后调用 不能 获取组件实例 this,组件实例还没被创建
beforeRouteUpdate (2.2) 路由复用同一个组件时, 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 this
beforeRouteLeave 离开当前路由时, 导航离开该组件的对应路由时调用,可以访问组件实例 this
26 组件之间的传值通信
组件之间通讯分为三种: 父传子、子传父、兄弟组件之间的通讯
1. 父组件给子组件传值
<template>
<child :msg="message"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
data () {
return {
message: 'father message';
}
}
}
</script>
子组件vue模板child.vue:
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>
2. 子组件向父组件通信
父组件向子组件传递事件方法,子组件通过$emit
触发事件,回调给父组件
父组件vue模板father.vue:
<template>
<child @msgFunc="func"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
methods: {
func (msg) {
console.log(msg);
}
}
}
</script>
子组件vue模板child.vue:
<template>
<button @click="handleClick">点我</button>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {
handleClick () {
//........
this.$emit('msgFunc');
}
}
}
</script>
3. 非父子, 兄弟组件之间通信
vue2中废弃了broadcast广播和分发事件的方法。父子组件中可以用props和$emit()。如何实现非父子组件间的通信,可以通过实例一个vue实例Bus作为媒介,要相互通信的兄弟组件之中,都引入Bus,然后通过分别调用Bus事件触发和监听来实现通信和参数传递。Bus.js可以是这样:
import Vue from 'vue'
export default new Vue()
在需要通信的组件都引入Bus.js:
<template>
<button @click="toBus">子组件传给兄弟组件</button>
</template>
<script>
import Bus from '../common/js/bus.js'
export default{
methods: {
toBus () {
Bus.$emit('on', '来自兄弟组件')
}
}
}
</script>
另一个组件也import Bus.js 在钩子函数中监听on事件
import Bus from '../common/js/bus.js'
export default {
data() {
return {
message: ''
}
},
mounted() {
Bus.$on('on', (msg) => {
this.message = msg
})
}
}
27 Vue与Angular以及React的区别?
Vue与AngularJS的区别
Angular采用TypeScript开发, 而Vue可以使用javascript也可以使用TypeScript
AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
AngularJS社区完善, Vue的学习成本较小
Vue与React的区别
vue组件分为全局注册和局部注册,在react中都是通过import相应组件,然后模版中引用;
props是可以动态变化的,子组件也实时更新,在react中官方建议props要像纯函数那样,输入输出一致对应,而且不太建议通过props来更改视图;
子组件一般要显示地调用props选项来声明它期待获得的数据。而在react中不必需,另两者都有props校验机制;
每个Vue实例都实现了事件接口,方便父子组件通信,小型项目中不需要引入状态管理机制,而react必需自己实现;
使用插槽分发内容,使得可以混合父组件的内容与子组件自己的模板;
多了指令系统,让模版可以实现更丰富的功能,而React只能使用JSX语法;
Vue增加的语法糖computed和watch,而在React中需要自己写一套逻辑来实现;
react的思路是all in js,通过js来生成html,所以设计了jsx,还有通过js来操作css,社区的styled-component、jss等;而 vue是把html,css,js组合到一起,用各自的处理方式,vue有单文件组件,可以把html、css、js写到一个文件中,html提供了模板引擎来处理。
react做的事情很少,很多都交给社区去做,vue很多东西都是内置的,写起来确实方便一些, 比如 redux的combineReducer就对应vuex的modules, 比如reselect就对应vuex的getter和vue组件的computed, vuex的mutation是直接改变的原始数据,而redux的reducer是返回一个全新的state,所以redux结合immutable来优化性能,vue不需要。
react是整体的思路的就是函数式,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以做到,比如结合redux-form,组件的横向拆分一般是通过高阶组件。而vue是数据可变的,双向绑定,声明式的写法,vue组件的横向拆分很多情况下用mixin
28 vuex是什么?怎么使用?哪种功能场景使用它?
vuex 就是一个仓库,仓库里放了很多对象。其中 state 就是数据源存放地,对应于一般 vue 对象里面的 data
state 里面存放的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据发生改变,依赖这相数据的组件也会发生更新
它通过 mapState 把全局的 state 和 getters 映射到当前组件的 computed 计算属性
vuex的使用借助官方提供的一张图来说明:
image
Vuex有5种属性: 分别是 state、getter、mutation、action、module;
state
Vuex
使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据
mutations
mutations
定义的方法动态修改Vuex 的 store 中的状态或数据。
getters
类似vue的计算属性,主要用来过滤一些数据
action
actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。
vuex 一般用于中大型 web 单页应用中对应用的状态进行管理,对于一些组件间关系较为简单的小型应用,使用 vuex 的必要性不是很大,因为完全可以用组件 prop 属性或者事件来完成父子组件之间的通信,vuex 更多地用于解决跨组件通信以及作为数据中心集中式存储数据。
使用Vuex解决非父子组件之间通信问题 vuex 是通过将 state 作为数据中心、各个组件共享 state 实现跨组件通信的,此时的数据完全独立于组件,因此将组件间共享的数据置于 State 中能有效解决多层级组件嵌套的跨组件通信问题
vuex 作为数据存储中心 vuex 的 State 在单页应用的开发中本身具有一个“数据库”的作用,可以将组件中用到的数据存储在 State 中,并在 Action 中封装数据读写的逻辑。这时候存在一个问题,一般什么样的数据会放在 State 中呢?目前主要有两种数据会使用 vuex 进行管理:1、组件之间全局共享的数据 2、通过后端异步请求的数据 比如做加入购物车、登录状态等都可以使用Vuex来管理数据状态
一般面试官问到这里vue基本知识就差不多了, 如果更深入的研究就是和你探讨关于vue的底层源码;或者是具体在项目中遇到的问题,下面列举几个项目中可能遇到的问题:
28 watch与computed的区别
computed:
computed是计算属性,也就是计算值,它更多用于计算值的场景
computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算 computed适用于计算比较消耗性能的计算场景
watch:
小结:
29、Vue是如何实现双向绑定的?
利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,然后根据变化进行后续响应,在vue3.0中通过Proxy代理对象进行类似的操作。
// 这是将要被劫持的对象
const data = {
name: '',
};
function say(name) {
if (name === '古天乐') {
console.log('给大家推荐一款超好玩的游戏');
} else if (name === '渣渣辉') {
console.log('戏我演过很多,可游戏我只玩贪玩懒月');
} else {
console.log('来做我的兄弟');
}
}
// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('get');
},
set: function(newVal) {
// 当属性值发生变化时我们可以进行额外操作
console.log(`大家好,我系${newVal}`);
say(newVal);
},
});
});
data.name = '渣渣辉';
//大家好,我系渣渣辉
//戏我演过很多,可游戏我只玩贪玩懒月
29 Vue2.x 响应式原理
Vue 采用数据劫持结合发布—订阅模式的方法,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
image
Watcher
订阅者是 Observer
和 Compile
之间通信的桥梁,主要做的事情
Vue3.x响应式数据原理
Vue3.x
改用Proxy
替代Object.defineProperty
。因为Proxy
可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
Proxy
只会代理对象的第一层,那么Vue3
又是怎样处理这个问题的呢?
判断当前Reflect.get的
返回值是否为Object
,如果是则再通过reactive
方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
我们可以判断key
是否为当前被代理对象target
自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger
30 v-model双向绑定原理
v-model
本质上是语法糖,v-model
在内部为不同的输入元素使用不同的属性并抛出不同的事件
text
和 textarea
元素使用 value 属性和 input 事件
checkbox
和 radio
使用 checked 属性和 change 事件
select
字段将 value 作为 prop 并将 change 作为事件
所以我们可以v-model进行如下改写:
<input v-model="sth" />
// 等同于
<input :value="sth" @input="sth = $event.target.value" />
//Parent
<template>
{{num}}
<Child v-model="num">
</template>
export default {
data(){
return {
num: 0
}
}
}
//Child
<template>
<div @click="add">Add</div>
</template>
export default {
props: ['value'],
methods:{
add(){
this.$emit('input', this.value + 1)
}
}
}
31 scoped样式穿透
scoped
虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除scoped
属性
使用/deep/
//Parent
<template>
<div class="wrap">
<Child />
</div>
</template>
<style lang="scss" scoped>
.wrap /deep/ .box{
background: red;
}
</style>
//Child
<template>
<div class="box"></div>
</template>
使用两个style标签
//Parent
<template>
<div class="wrap">
<Child />
</div>
</template>
<style lang="scss" scoped>
//其他样式
</style>
<style lang="scss">
.wrap .box{
background: red;
}
</style>
//Child
<template>
<div class="box"></div>
</template>
32 ref的作用
33 computed和watch区别
当页面中有某些数据依赖其他数据进行变动的时候,可以使用计算属性computed
Computed
本质是一个具备缓存的watcher
,依赖的属性发生变化就会更新视图。适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理
image
<template>{{fullName}}</template>
export default {
data(){
return {
firstName: 'xie',
lastName: 'yu fei',
}
},
computed:{
fullName: function(){
return this.firstName + ' ' + this.lastName
}
}
}
watch
用于观察和监听页面上的vue实例,如果要在数据变化的同时进行异步操作或者是比较大的开销,那么watch
为最佳选择
Watch
没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开deep:true
选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式监听,如果没有写到组件中,不要忘记使用unWatch
手动注销
image
<template>{{fullName}}</template>
export default {
data(){
return {
firstName: 'xie',
lastName: 'xiao fei',
fullName: 'xie xiao fei'
}
},
watch:{
firstName(val) {
this.fullName = val + ' ' + this.lastName
},
lastName(val) {
this.fullName = this.firstName + ' ' + val
}
}
}
34 vue-router守卫
导航守卫 router.beforeEach
全局前置守卫
to: Route
: 即将要进入的目标(路由对象)
from: Route
: 当前导航正要离开的路由
next: Function
: 一定要调用该方法来 resolve
这个钩子。(一定要用这个函数才能去到下一个路由,如果不用就拦截)
执行效果依赖 next 方法的调用参数。
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false)
:取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
路由独享的守卫 你可以在路由配置上直接定义 beforeEnter
守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫你可以在路由组件内直接定义以下路由导航守卫
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用,我们用它来禁止用户离开
// 可以访问组件实例 `this`
// 比如还未保存草稿,或者在用户离开前,
将setInterval销毁,防止离开之后,定时器还在调用。
}
}
35 vue修饰符
stop
:阻止事件的冒泡
prevent
:阻止事件的默认行为
once
:只触发一次
self
:只触发自己的事件行为时,才会执行
36 vue项目中的性能优化
37 vue.extend和vue.component
38 Vue的SPA 如何优化加载速度
减少入口文件体积
静态资源本地缓存
开启Gzip压缩
使用SSR,nuxt.js
39 移动端如何设计一个比较友好的Header组件?
当时的思路是头部(Header)一般分为左、中、右三个部分,分为三个区域来设计,中间为主标题,每个页面的标题肯定不同,所以可以通过vue props的方式做成可配置对外进行暴露,左侧大部分页面可能都是回退按钮,但是样式和内容不尽相同,右侧一般都是具有功能性的操作按钮,所以左右两侧可以通过vue slot插槽的方式对外暴露以实现多样化,同时也可以提供default slot默认插槽来统一页面风格
40 Proxy与Object.defineProperty的优劣对比?
Proxy的优势如下:
Proxy可以直接监听对象而非属性
Proxy可以直接监听数组的变化
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的
Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
Object.defineProperty的优势如下:
兼容性好,支持IE9
41 你是如何理解Vue的响应式系统的?
image
响应式系统简述:
任何一个 Vue Component 都有一个与之对应的 Watcher 实例。
Vue 的 data 上的属性会被添加 getter 和 setter 属性。
当 Vue Component render 函数被执行的时候, data 上会被 触碰(touch), 即被读, getter 方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有 data。(这一过程被称为依赖收集)
data 被改动时(主要是用户操作), 即被写, setter 方法会被调用, 此时 Vue 会去通知所有依赖于此 data 的组件去调用他们的 render 函数进行更新。
42 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异?
现代前端框架有两种方式侦测变化,一种是pull一种是push
pull: 其代表为React,我们可以回忆一下React是如何侦测到变化的,我们通常会用setStateAPI显式更新,然后React会进行一层层的Virtual Dom Diff操作找出差异,然后Patch到DOM上,React从一开始就不知道到底是哪发生了变化,只是知道「有变化了」,然后再进行比较暴力的Diff操作查找「哪发生变化了」,另外一个代表就是Angular的脏检查操作。
push: Vue的响应式系统则是push的代表,当Vue程序初始化的时候就会对数据data进行依赖的收集,一但数据发生变化,响应式系统就会立刻得知,因此Vue是一开始就知道是「在哪发生变化了」,但是这又会产生一个问题,如果你熟悉Vue的响应式系统就知道,通常一个绑定一个数据就需要一个Watcher,一但我们的绑定细粒度过高就会产生大量的Watcher,这会带来内存以及依赖追踪的开销,而细粒度过低会无法精准侦测变化,因此Vue的设计是选择中等细粒度的方案,在组件级别进行push侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件,然后在组件内部进行Virtual Dom Diff获取更加具体的差异,而Virtual Dom Diff则是pull操作,Vue是push+pull结合的方式进行变化侦测的
43 Vue为什么没有类似于React中shouldComponentUpdate的生命周期?
考点: Vue的变化侦测原理
前置知识: 依赖收集、虚拟DOM、响应式系统
根本原因是Vue与React的变化侦测方式有所不同
React是pull的方式侦测变化,当React知道发生变化后,会使用Virtual Dom Diff进行差异检测,但是很多组件实际上是肯定不会发生变化的,这个时候需要用shouldComponentUpdate进行手动操作来减少diff,从而提高程序整体的性能.
Vue是pull+push的方式侦测变化的,在一开始就知道那个组件发生了变化,因此在push的阶段并不需要手动控制diff,而组件内部采用的diff方式实际上是可以引入类似于shouldComponentUpdate相关生命周期的,但是通常合理大小的组件不会有过量的diff,手动优化的价值有限,因此目前Vue并没有考虑引入shouldComponentUpdate这种手动优化的生命周期.
44 Vue中的key到底有什么用?
key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速
diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.
diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.
准确: 如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug. 快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n)
,Map
的时间复杂度仅仅为O(1)
.
image
45 vue 项目性能优化
代码层面:
Webpack 层面优化:
46 nextTick
nextTick
可以让我们在下次 DOM
更新循环结束之后执行延迟回调,用于获得更新后的 DOM
nextTick
主要使用了宏任务和微任务。根据执行环境分别尝试采用
Promise
MutationObserver
setImmediate
如果以上都不行则采用setTimeout
定义了一个异步方法,多次调用nextTick
会将方法存入队列中,通过这个异步方法清空当前队列
47 说一下vue2.x中如何监测数组变化
使用了函数劫持的方式,重写了数组的方法,Vue
将data
中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。
48 你的接口请求一般放在哪个生命周期中
接口请求一般放在mounted
中,但需要注意的是服务端渲染时不支持mounted
,需要放到created
中
49 组件中的data为什么是一个函数
一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data
是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data
不冲突,data必须是一个函数
50 说一下v-model的原理
v-model
本质就是一个语法糖,可以看成是value + input
方法的语法糖。可以通过model
属性的prop
和event
属性来进行自定义。原生的v-model
,会根据标签的不同生成不同的事件和属性
51 Vue事件绑定原理说一下
原生事件绑定是通过addEventListener
绑定给真实元素的,组件事件绑定是通过Vue
自定义的$on
实现的
52 Vue模版编译原理知道吗,能简单说一下吗?
简单说,Vue
的编译过程就是将template
转化为render
函数的过程。会经历以下阶段:
生成AST
树
优化
codegen
首先解析模版,生成AST
语法树(一种用JavaScript
对象的形式来描述整个模板)。使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
Vue
的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的AST
树转换为可执行的代码
53 Vue2.x和Vue3.x渲染器的diff算法分别说一下
简单来说,diff
算法有以下过程
同级比较,再比较子节点
先判断一方有子节点一方没有子节点的情况(如果新的children
没有子节点,将旧的子节点移除)
比较都有子节点的情况(核心diff
)
递归比较子节点
正常Diff
两个树的时间复杂度是O(n^3)
,但实际情况下我们很少会进行跨层级的移动DOM
,所以Vue
将Diff
进行了优化,从O(n^3) -> O(n)
,只有当新旧children
都为多个子节点时才需要用核心的Diff
算法进行同层级比较。
Vue2
的核心Diff
算法采用了双端比较的算法,同时从新旧children
的两端开始进行比较,借助key
值找到可复用的节点,再进行相关操作。相比React
的Diff
算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅
在创建VNode
时就确定其类型,以及在mount/patch
的过程中采用位运算来判断一个VNode
的类型,在这个基础之上再配合核心的Diff
算法,使得性能上较Vue2.x
有了提升
54 再说一下虚拟Dom以及key属性的作用
由于在浏览器中操作DOM
是很昂贵的。频繁的操作DOM
,会产生一定的性能问题。这就是虚拟Dom的产生原因
Virtual DOM
本质就是用一个原生的JS对象去描述一个DOM
节点。是对真实DOM的一层抽象
VirtualDOM
映射到真实DOM要经历VNode
的create
、diff
、patch
等阶段
key的作用是尽可能的复用 DOM 元素
55 Vue中组件生命周期调用顺序说一下
加载渲染过程
父beforeCreate
->父created
->父beforeMount
->子beforeCreate
->子created
->子beforeMount
- >子mounted
->父mounted
子组件更新过程
父beforeUpdate
->子beforeUpdate
->子updated
->父updated
父组件更新过程
父 beforeUpdate
-> 父 updated
销毁过程
父beforeDestroy
->子beforeDestroy
->子destroyed
->父destroyed
56 SSR了解吗
SSR
也就是服务端渲染,也就是将Vue
在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
SSR
有着更好的SEO
、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate
和created
两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js
的运行环境。还有就是服务器会有更大的负载需求
57 你都做过哪些Vue的性能优化
编码阶段
SEO优化
打包优化
用户体验
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip
压缩等。
58 Vue.js特点
59 请说出vue.cli项目中src目录每个文件夹和文件的用法
assets
文件夹是放静态资源;
components
是放组件;
router
是定义路由相关的配置;
view
视图;
app.vue
是一个应用主组件;
main.js
是入口文件
60 vue路由传参数
61 vuex 是什么?有哪几种属性?
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式。
有 5 种,分别是 state
、getter
、mutation
、action
、module
vuex
的 store
是什么?
vuex
就是一个仓库,仓库里放了很多对象。其中 state
就是数据源存放地,对应于一般 vue 对象里面的 datastate
里面存放的数据是响应式的,vue
组件从 store
读取数据,若是 store
中的数据发生改变,依赖这相数据的组件也会发生更新它通过 mapState
把全局的 state
和 getters
映射到当前组件的 computed
计算属性
vuex 的 getter 是什么?
vuex 的 mutation 是什么?
vuex 的 action 是什么?
action
类似于 muation
, 不同在于:action
提交的是 mutation
,而不是直接变更状态action
可以包含任意异步操作
vue
中 ajax
请求代码应该写在组件的 methods
中还是 vuex
的 action
中
vuex
的 module
是什么?
面对复杂的应用程序,当管理的状态比较多时;我们需要将vuex
的store
对象分割成模块(modules
)。
如果请求来的数据不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入 vuex
的 state
里如果被其他地方复用,请将请求放入 action
里,方便复用,并包装成 promise
返回
62 如何让CSS只在当前组件中起作用?
将当前组件的<style>
修改为<style scoped>
63 delete和Vue.delete删除数组的区别?
var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[0]
console.log(a) //[empty,2,3,4]
this.$delete(b,0)
console.log(b) //[2,3,4]
64 v-on可以监听多个方法吗?
可以
<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />
v-on 常用修饰符
.stop
该修饰符将阻止事件向上冒泡。同理于调用 event.stopPropagation()
方法
.prevent
该修饰符会阻止当前事件的默认行为。同理于调用 event.preventDefault()
方法
.self
该指令只当事件是从事件绑定的元素本身触发时才触发回调
.once
该修饰符表示绑定的事件只会被触发一次
65 Vue子组件调用父组件的方法
66 vue如何兼容ie的问题
babel-polyfill插件
67 Vue 改变数组触发视图更新
以下方法调用会改变原始数组:push()
, pop()
, shift()
, unshift()
, splice()
, sort()
, reverse()
,Vue.set( target, key, value )
68 DOM 渲染在哪个周期中就已经完成?
在mounted
注意 mounted
不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick
替换掉 mounted
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
69 简述每个周期具体适合哪些场景
beforecreate
: 可以在这加个loading
事件,在加载实例时触发
created
: 初始化完成时的事件写在这里,如在这结束loading
事件,异步请求也适宜在这里调用
mounted
: 挂载元素,获取到DOM节点 updated
: 如果对数据统一处理,在这里写上相应函数
beforeDestroy
: 可以做一个确认停止事件的确认框
第一次加载会触发哪几个钩子
会触发beforeCreate
, created
,beforeMount
,mounted
70 动态绑定class
active
classname
, isActive
变量
<div :class="{ active: isActive }"></div>