Vue
基础
1 谈谈什么是SPA
仅在项目初始化的时候加载所有的HTML、CSS、JavaScript,后面通过前端路由完成页面内容的切换。
- 首页加载慢,后期页面切换快
- 服务器压力小
- 浏览器前进后退会有问题
2 谈谈什么是MVVM
Model–View–ViewModel软件架构设计模式。数据Model会绑定到ViewModel,ViewModel自动将数据渲染到View中,View变化的时候会通过ViewModel来更新Model。
<!-- View层 -->
<div id="app">
<p>{{message}}</p>
<button v-on:click="showMessage()">Click me</button>
</div>
// ViewModel层
var app = new Vue({
el: "#app",
data: {
// 用于描述视图状态
message: "Hello Vue!",
},
methods: {
// 用于描述视图行为
showMessage() {
let vm = this;
alert(vm.message);
},
},
created() {
let vm = this;
// Ajax 获取 Model 层的数据
ajax({
url: "/your/server/data/api",
success(res) {
vm.message = res;
},
});
},
});
// Model层
{
"url": "/your/server/data/api",
"res": {
"success": true,
"name": "IoveC",
"domain": "www.cnblogs.com"
}
}
3 Class和style 动态绑定
主要通过对象和数组两种方法
对象形式:
<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
data: {
isActive: true,
hasError: false
}
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
数组形式:
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
<div v-bind:style="[baseStyles, overridingStyles]"></div>
data: {
baseStyles: {
color: 'green'
},
overridingStyles: {
width: '100px';
}
}
4 v-show和v-if
v-if惰性加载,需要时才会去渲染相关模块v-show总是会渲染,只是通过display属性控制显示
5 computed和watch区别
computed依赖其他属性,并且有缓存,只有当依赖的属性值发生变化时,才会去更新computed的值watch则是对数据的监听回调
computed用于数值计算,watch用于监听数据变化时执行相关异步操作
Q1:computed的实现原理
A:Vue为computed内部实现了一个惰性的watcher和一个Dep实列,当computed对于的依赖发生改变时,先查询Dep中是否有订阅者,有的话再去比较新旧值,有变化的时候才去更新。
- 只有当需要读取属性值时,才会去重新计算
- 最终值改变时才会重新渲染
// 不会重新渲染
computed = a + b + c = 6
||
computed = a + e + f = 6
Q:vm.$watch原理
A:对watcher和dep的封装
6 组件中data为什么是个函数
因为组件可以被复用,如果是个对象的话组件在复用的时候,没有作用域隔离,data容易被修改。
函数的话,返回一个对象的独立拷贝。维护各自的data对象。
7 谈谈Vue的单向数据流
父组件prop的更新会向下流动到子组件中,反过来不行。避免组件间数据流难以跟踪
Q: 如果子组件必须要改父组件的prop呢
A:子组件通过$emit向上派发一个事件,父组件接受后修改
8 谈谈v-model原理
本质是语法糖,在不同的表单元素间通过使用属性和事件来实现
text和textarea元素使用value属性和input事件checkbox和radio使用checked属性和change事件select字段将value作为prop并将change作为事件
<input v-model="sth">
// 等于
<input :value="sth" @input="sth = $event.targent.value">
自定义组件:
父组件:
<ModelChild v-model="message"></ModelChild>
子组件:
<div>{{value}}</div>
props: {
value: String
},
methods: {
test1() {
this.$emit('input', '小红')
},
},
9 谈谈Vue的生命周期
生命周期就是一个Vue实例,从创建到销毁的一系列过程。在每个阶段都有对应的函数(钩子)可以进行相应的操作。
beforeCreate:new Vue之后调用的第一个钩子,此时组件上的data methods等都不可用
created:初始化data属性的绑定,真实DOM还没生成
beforeMount: 挂载前,render函数第一次被调用,虚拟DOM生成
mounted:真实DOM挂载完毕,data完成双向绑定,el被$.el替代
beforeUpdate: 数据更新前,可以修改数据
updated: 真实DOM更新完毕
beforeDestroy: 组件被卸载前,可以清空定时器
destroyed: 组件被卸载,数据无
Q1:接口请求放在哪个钩子
A:created、beforeMount、mounted都可以,推荐created。主要是1.data已经初始化,更早请求更早获取;2.SSR不支持beforeMount、mounted
Q2:从哪个钩子开始可以操作DOM
A:mounted中,真实DOM已经挂载
10 父子组件钩子函数的执行顺序
渲染阶段:
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount ->子mounted->父mounted
更新阶段:
父beforeUpdate->子beforeUpdate->子Updated->父Updated
销毁阶段:
父beforeDestroy->子beforeDestroy->子Destroyed->父Destroyed
11 父组件如何监听子组件的生命周期
方法1:
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit('mounted');
}
方法2:
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父组件监听到mounted钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发mounted钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
12 组件间通信方式
- props和$emit
父子间通信
- ref和$parent
父子间通信
- EventBus
通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信
- Vuex
13 什么是SSR
将Vue在客户端把标签渲染成HTML的工作放在服务端,服务端把HTML直接返回给客户端
- 更好的SEO,SPA中数据通过请求获取,而SSR页面和数据由服务端一起返回
- 首屏更快
- 只支持beforeCreate和created
- 服务端NodeJS渲染比只提供静态文件更消耗CPU性能
进阶
14 Vue2.x响应式原理
依赖收集:
通过defineProperty给data添加setter和getter方法;通过创建Dep,收集使用到data的Watcher
编译模板的时候会创建Watcher,并将Dep中的target指向当前的Watcher,如果使用到data就会触发getter方法,然后通过Dep.addSub将Watcher收集起来
派发更新:
数据更新的时候会触发setter方法,判断subs是否有订阅者watcher,有的话然后通过notify调用watcher的update方法更新视图
15 Vue2.x如何监测数组的变化
两种特殊情况:
- 通过下标直接修改数组内对应的值
- 修改数组的长度
第一种:使用vm.$set()。详见下一节
第二种:数组原型拦截。重写了数组的7个方法,先获取数组的observer对象,有新值就调用便利所有值使用observe监听,手动调用notify
// 以下7个方法被重写
push、pop、shift、unshift、splice、sort、reverse
16 vm.$set()实现原理
vm.$set(target, key, value)
-
如果是数组,则使用Vue重新的splice方法方法实现响应式
-
如果是对象,会先判读属性是否存在、获取对象的ob判断是否是响应式,根据判断要么直接赋值要么就调用defineReactive来实现响应式
17 Proxy与Object.defineProperty
- Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历
- Proxy存在浏览器兼容性问题
- Proxy可以直接监听对象和数组,还可以代理动态增加的属性
18 Vue中的Virtual-DOM
Virtual-DOM基本流程:
- 用JS对象模拟真实DOM树(记录节点的类型、属性、子节点等)
- 比较两棵Virtual-DOM树的差异(Diff算法)
- 将两个Virtual-DOM对象的差异应用到真正的DOM树(Patch算法)
Vue中Virtual-DOM实现:
- Vue中用VNode这个class描述Virtual-DOM,通过_createElement方法来创建VNode
- 当oldVnode不存在时,直接创建新的节点。存在时会调用sameVnode进行基本属性的比较,相同才进行Diff,不同的话会删掉老Vnode创建新Vnode
- Diff过程,首先进行文本节点的判断, 没有文本节点的情况下,进入子节点的Diff
21 Vue中key的作用
diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,从而找到相应旧节点
20 Vue2.x和Vue3的Diff算法比较
Vue2.x:
1、先找到 不需要移动的相同节点,借助key值找到可复用的节点是,消耗最小
2、再找相同但是需要移动的节点,消耗第二小
3、最后找不到,才会去新建删除节点,保底处理
注意:对比过程不会对Vnode进行修改,而直接对真实DOM修改,Vue的patch是即时的,并不是打包所有修改最后一起操作DOM
Vue3:
最强增长序列,
V2里面会优先处理头头比对,头尾比对,尾头比对;
V3根据新老节点索引列表找到最长稳定序列,通过最长增长子序列的算法比对,找出新旧节点中不需要移动的节点,原地复用,仅对需要移动或已经patch的节点进行操作
21 Vue中渲染过程
22 Vue中的事件机制
Vue事件机制本质上就是一个发布-订阅模式的实现
23 nextTick原理
JS是单线程的,代码执行是基于事件循环。在执行完一个宏任务及其对应的微任务队列后,再执行下一个宏任务前,会进行UI渲染。
Vue在更新DOM时是异步执行的。只要监听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更
如果同一个watcher被多次触发,只会被推入到队列中一次
nextTick方法会在队列中加入一个回调函数,确保该函数在前面的DOM操作完成后才调用
总结:
- 同一事件循环中的宏任务和对应微任务执行完成->DOM更新->执行Vue.nextTick()的回调
- nextTick实现基于MutationObserver API,给它绑定回调,得到MO实例,这个回调会在MO实例监听到变动时触发
场景:
- 在created中执行的DOM操作要放到Vue.nextTick回调中
- 在修改数据之后立即使用这个方法,获取更新后的DOM
// 创建MO实例
const observer = new MutationObserver(callback)
const textNode = '想要监听的DOM'
observer.observe(textNode, {
characterData: true // 说明监听文本内容的修改
})
24 谈谈keep-alive原理
虚拟组件,不会渲染DOM
实例会缓存对应组件的VNode,如果命中缓存,直接从缓存对象返回对应VNode
接受3个变量include、exclude、max
LRU缓存淘汰算法
25 Vue-Router路由模式及实现原理
- hash: 使用URL的hash值来作路由。支持所有浏览器
- history: 依赖HTML5 History API和服务器配置
- abstract: 支持所有JavaScript运行环境,如Node.js服务器端。如果发现没有浏览器的API,路由会自动强制进入这个模式
1、hash模式
在域名之后带有一个#
- 通过loaction.hash获取当前的URL的hash
- 通过hashchange方法监听URL中hash的变化
- hash值的改变会记录到浏览器历史中
push将新路由添加到浏览器访问历史的栈顶
- $router.push() //调用方法
- HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)
- History.transitionTo() //监测更新,更新则调用History.updateRoute()
- History.updateRoute() //更新路由
- {app._route= route} //替换当前app路由
- vm.render() //更新视图
replace替换掉当前的路由添加到浏览器访问历史的栈顶
2、history模式
HTML5 提供的 History API 来实现
- 通过loaction.pathname获取当前的URL的路由地址
- history.pushState和history.repalceState修改URL地址,只有前者记录浏览器历史
- popState监听URL中路由的变化
Q:路由懒加载
A:把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件
// vue异步组件和webpack的动态`import`
const Foo = () => import('./Foo.vue')
26 Vue-Router导航守卫和路由钩子
全局:router.beforeEach router.beforeResolve router.afterEach
路由独享的守卫:beforeEnter
组件内的守卫:beforeRouteEnter beforeRouteUpdate beforeRouteLeave
完整的导航解析流程:
1.失活组件调用beforeRouteLeave
2.调用全局的钩子beforeEach
*3.如果是复用的组件beforeRouteUpdate
4.路由独享beforeEnter
5.激活的组件beforeRouteEnter
6.全局的beforeResolve
7.全局的afterEach
8.触发DOM更新
9.调用beforeRouteEnter中传给next的回调,对应组件作为回调的参数
注:捕获路由钩子中的错误,router.onError;afterEach只接受to、from不接受next
27 Vuex
跨组件通信、数据响应式、数据持久化
- State:定义了应用状态的数据结构,可以在这里设置默认值。
- Getter:允许组件从Store中获取数据,mapGetters辅助函数仅仅是将store中的getter映射到局部计算属性。
- Mutation:是唯一更改store中状态的方法,且必须是同步函数。
- Action:用于提交mutation,而不是直接变更状态,可以包含任意异步操作。
- Module:允许将单一的Store拆分为多个store且同时保存在单一的状态树中。
28 Vue3.0其他新特性
待补充
29 Vue项目优化
- Web层
- Webpack层
- 代码层
详细可以参考下:Vue 项目性能优化 — 实践指南
30 踩过的坑
待补充