1.Vue 生命周期分别做了什么事情
| hooks | status | 实践 |
|---|---|---|
| beforeCreate | 实例初始化之后,this 指向创建的实例,此时不能访问到 data、methods 等属性 | 常用于初始化非响应式变量 |
| created | 实例创建完成,此时可访问 data、method 等属性;不能访问到$el,无法获取组件实例 | 常用于简单的 ajax 请求,页面的初始化 |
| beforeMount | 在挂载开始之前被调用,进行模版编译,把 template 编译成 render 函数 | -- |
| mounted | 实例挂载到 DOM 上,此时可以访问$el、$ref,因此可以获取到 DOM 节点 | 常用于获取 VNode 信息和操作,ajax 请求 |
| beforeUpdate | 响应式数据更新时调用,获取的是更新前的 DOM | 手动移除已添加的事件监听器量 |
| updated | 组件更新后立刻调用,可执行依赖于最新 DOM 的操作 | 避免在这个钩子函数中操作数据,可能陷入死循环 |
| beforeDestroy | 实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作 |
| destroyed | 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | -- |
2.keep-alive 及其生命周期
WARNING
keep-alive 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中
| hooks | status |
|---|---|
| activated | 在第一次渲染时调用,以及后续每次激活时调用 |
| deactivated | 组件停用时调用 |
3.v-for 为什么要加上 key
提升性能
① Vue 通过 diff 算法去比较 VDom 的变化,而 key 则为每个 VNode 添加了唯一标识,使得 diff 算法可以更高效的比较 VDom 的变化
② 如果 v-for 用于渲染组件,不提供 key 的话,每次重新渲染都会创建新的实例,并销毁旧的。而新的组件实例不会继承原有的状态,因此会造成组件内部状态丢失的问题,如输入框中已输入的内容
为什么不建议用 index 作为 key
index 有可能会变,起不到唯一标识的作用
const arr = [
{ age: 18, name: 'vladimir' },
{ age: 19, name: 'screeper' },
{ age: 20, name: 'coder' },
{ age: 24, name: 'ghost' }
]
arr.unshift({ age: 23, name: 'codemaker' })const arr = [
{ age: 18, name: 'vladimir' },
{ age: 19, name: 'screeper' },
{ age: 20, name: 'coder' },
{ age: 24, name: 'ghost' }
]
arr.unshift({ age: 23, name: 'codemaker' })在数组的头部插入新元素,则原数组的所有 index 值都发生了改变,因此 key 的效果无法达到
4.谈谈 Vue 响应式 和 双向绑定 的原理
当数据发生变化时,对应的视图也随之更新
响应式
响应式一定是:数据与函数的绑定,这里的函数一般指的是用到此数据的渲染函数
此外,计算属性 computed 也是此原理,利用数据与回调函数进行绑定,实现了数据之间的关联,关联的桥梁正是回调函数
双向绑定
Vue2 基于 Object.defineProperty,并结合发布订阅模式
Vue3 基于 Proxy
已 Vue2 为例,通过遍历 data 对象的所有属性,使其转化为 getter 和 setter
在 getter 里进行依赖收集,即记录用到此变量的所有函数
在 setter 里进行派发更新,即运行刚刚记录到的所有渲染函数
const observe = obj => {
for (let key in obj) {
let internalValue = obj[key]
let funcs = new Set()
Object.defineProperty(obj, key, {
get: function () {
// 依赖收集, 记录:哪个函数用到了此变量
window.__func && funcs.add(window.__func)
return internalValue
},
set: function (val) {
internalValue = val
// 派发更新, 运行:执行所有用到此变量的函数
funcs.forEach(func => {
func()
})
}
})
}
}
// 辅助函数,把函数名记录到全局变量上,方便进行依赖收集
const autoRun = fn => {
window.__func = fn
fn()
window.__func = null
}const observe = obj => {
for (let key in obj) {
let internalValue = obj[key]
let funcs = new Set()
Object.defineProperty(obj, key, {
get: function () {
// 依赖收集, 记录:哪个函数用到了此变量
window.__func && funcs.add(window.__func)
return internalValue
},
set: function (val) {
internalValue = val
// 派发更新, 运行:执行所有用到此变量的函数
funcs.forEach(func => {
func()
})
}
})
}
}
// 辅助函数,把函数名记录到全局变量上,方便进行依赖收集
const autoRun = fn => {
window.__func = fn
fn()
window.__func = null
}5. Proxy 与 defineProperty 的异同?以及 Vue3 为什么要用 Proxy 替代?
Proxy 和 defineProperty 都是 JavaScript 中用于拦截和改变对象属性行为的工具
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
Object.defineProperty
Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
可见,defineProperty 是针对对象特定的属性进行读写操作的拦截,而Proxy 可以拦截所有的基本操作,包括读取原型和数组的相关处理,即回归到了对象的最初状态
因此,在 Proxy 中重新定义对象的基本操作,即可实现 Vue 中响应式的效果(依赖收集 和 派发更新)
defineProperty 是如何处理数组的
上述提到,defineProperty 只是针对对象特定的属性进行 读写 操作的拦截,因此当数组发生改变时(包括通过index进行更改,和通过method进行更改),defineProperty 无法监听到,也就无法执行相关的响应式操作
因此,在 Vue2 中,对于 data 中定义的数组,修改其继承关系,在数组对象与数组原型之间加入一层自定义对象,在这层对象中重写了数组的全部方法,以此达到监听的效果
arr(数组对象) -> 自定义对象(隐式原型对象proto指向数组的显式原型) -> Array.prototype
由此可见,使用 Proxy 使得 Vue3 的响应式更为完整,功能更为强大
6. 对虚拟DOM的理解
虚拟DOM的本质
虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构
在vue中,每个组件都有一个render函数,每个render函数都会返回一个虚拟dom树,这也就意味着每个组件都对应一棵虚拟DOM树

使用原因
在vue中,渲染视图会调用render函数,这种渲染不仅发生在组件创建时,同时发生在视图依赖的数据更新时。如果在渲染时,直接使用真实DOM,由于真实DOM的创建、更新、插入等操作会带来大量的性能损耗,从而就会极大的降低渲染效率。
因此,vue在渲染时,使用虚拟dom来替代真实dom,主要为解决渲染效率的问题。
过程
编译
vue框架中有一个compile模块,它主要负责将模板转换为render函数,而render函数调用后将得到虚拟dom。
编译的过程分两步:
- 将模板字符串转换成为
AST - 将
AST转换为render函数
如果使用传统的引入方式,则编译时间发生在组件第一次加载时,这称之为运行时编译。
如果是在vue-cli的默认配置下,编译发生在打包时,这称之为模板预编译。
编译是一个极其耗费性能的操作,预编译可以有效的提高运行时的性能,而且,由于运行的时候已不需要编译,vue-cli在打包时会排除掉vue中的compile模块,以减少打包体积
模板的存在,仅仅是为了让开发人员更加方便的书写界面代码
vue最终运行的时候,最终需要的是render函数,而不是模板,因此,模板中的各种语法,在虚拟dom中都是不存在的,它们都会变成虚拟dom的配置
转换
在一个组件实例首次被渲染时,它先生成虚拟dom树,然后根据虚拟dom树创建真实dom,并把真实dom挂载到页面中合适的位置,此时,每个虚拟dom便会对应一个真实的dom。
如果一个组件受响应式数据变化的影响,需要重新渲染时,它仍然会重新调用render函数,创建出一个新的虚拟dom树,用新树和旧树对比,通过对比,vue会找到最小更新量,然后更新必要的虚拟dom节点,最后,这些更新过的虚拟节点,会去修改它们对应的真实dom
这样一来,就保证了对真实dom达到最小的改动。
