特别提示:
本文不成文,谨慎阅读。
《前端面试总结点线面》系列是为了收拢杂而乱的前端领域知识,由点及线,再涉及面,目的是为帮助广大前端同学复习巩固或查漏补缺或增删改查,为了更好的建立前端领域知识体系,为了更好的为前端面试做好准备,从而做一个合格、进步的前端开发工程师。
Vue 3.0
-
特性
- 更小
- 全局 API,内置组件、功能 tree-shaking
- 10kb gzipped
- 更快
- 基于 proxy 的变动侦测,性能整体优于 getter/setter
- Virtual DOM 重构: a). 编译模版的静态标记;b). 时间缓存;c). 静态提升
- 编译器架构重构,更多的编译时优化
- 加强 API 设计一致性
- TypeScript 支持
- 采用 monorepo 结构,分层清晰
- 更小
-
Composition API
reactive创建一个响应式对象,对应于 2.0 的Vue.observable()。setup() { const obj = reactive({count: 0, title: 'Vue 3.0'}) }ref创建一个包装对象,ref 对象具有指向内部值的.valueconst count = ref(0) console.log(count.value) // 0computed计算属性const computedCount = computed(() => count*100)watch监听数据watch(() => obj.count, (count, prevCount) => {})- lifeCycle
原方法 now beforeCreatesetup createdsetup beforeMount onBeforeMount mounted onMounted beforeUpdate onBeforeUpdate updated onUpdated beforeDestory onBeforeUnmount destoryed onUnmounted
MVVM
目标
- View 层的组件有一定处理输入并作出反馈的能力
- View 层的每一个组件应该来自 UI 库,UI 库的作者并不一定知道业务
- View 层的组装是由完全声明式的代码完成的,他可以由设计师通过工具直接生成的
- Model 层的编写是完全自由的,其中是普通的 JavaScript 对象,不能假设。
架构
View <---> ViewModel <---> Model
View --- 命令 ---> ViewModel View <--双向绑定--> ViewModel
原理
Vue 双向绑定

- 监听数据变化 - 观察者
- 对 data 进行改造,对所有属性重写设置 get/set 方法,当获取属性或者设置属性时,添加对应逻辑
- Object.defineProperty
- Proxy
- 对 data 进行改造,对所有属性重写设置 get/set 方法,当获取属性或者设置属性时,添加对应逻辑
- 管理订阅者 - Dep
- 对订阅者进行存储、收集和通知
class Dep { constructor() { this.subs = [] } addSub(sub) { // 收集订阅者 this.subs.push(sub) } notify() { // 通知所有的订阅者(Watcher),触发更新 this.subs.forEach(sub => sub.update()) } }
- 对订阅者进行存储、收集和通知
- 订阅者 - Watcher
- 每个订阅者都是对某条数据的订阅
- 订阅者维护者每一次更新之前的数据,将其和更新之后的数据进行对比,如发生变化就执行相应业务逻辑,并更新订阅者中维护的数据的值。
class Watcher { this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() }
基于数据劫持的双向绑定原理:
- 利用 Proxy 或 Object.defineProperty 生成的 Observer 对对象/属性进行劫持,在属性发生变化后通知订阅者
- 解析器(Compile)解析模版中的 Directive 指令,收集指令依赖的方法和数据,等待数据变化然后渲染
- Watcher 属于 Observer 和 Compile 之间的桥梁,它将接收到的 Oberser 产生的数据变化,并根据 Compile 提供的指令进行视图渲染,使得数据变化促使视图变化。
Object.defineProperty 缺陷:
- 无法监听数组的变化
- 利用索引修改数组
item[index] = newValVue.set(example1.items, indexOfItem, newValue)
- 修改数组的长度
items.length = newLengthitems.splice(newLength)
- 监听数组需要遍历一遍数组挨个劫持,性能太差。其次 push 这种不改变原有索引的方法无法触发 setter,还需要再次遍历劫持。
- 利用索引修改数组
- 深度递归监听属性,一次性计算量大
- 无法监听新增/删除属性
Proxy
- 可以直接监听对象而非属性
const newObj = new Proxy(obj, { get(target, key, receiver) { return Reflect.get(target, key, receiver) }, set(target, key, val, receiver) { return Reflect.set(target, key, val, receiver) } }) - Proxy 可以直接监听数组的变化
vdom diff
h() => vnode() => patch(patchVnode) diff => addVnodes removeVodes => updateChildren
问题
-
父子组件声明周期钩子
- 加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted - 子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated - 父组件更新过程
父beforeUpdate->父updated - 销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
- 加载渲染过程
-
首页白屏 原因:
- 首页资源需要下载(js,css)
- SPA 单页应用 DOM 是需要 js 执行生成的,js 执行需要时间。 解决方案:
- 首页直出,节省 js 生成 dom 时间,直接渲染
- 减少静态资源包的大小
- 代码拆分,动态 import,prefetch
- 合理使用缓存,HTTP/2
- 常规操作:CDN,loading,骨架屏
-
nextTick Vue.js 在触发某个数据的 setter 之后,对应的 Watcher 订阅者对象会被 push 进一个队列 queue,在下一个 tick 的时候将这个队列的任务全部拿出来 run 执行一遍。 vuejs 源码中在 microtask 上实现了一个 nextTick,在当前调用栈执行完之后才会去执行这个事件。
-
computed 🆚 watch
- computed 计算属性
- 计算属性本质上是
computed watcher- computed watcher 并不会立刻求值,同时持有一个 dep 实例
- 计算属性适合用在模版渲染中,某个值是依赖了其他响应式对象甚至是计算属性计算而来
- computed 本身是通过代理的方式代理到组件实例上的,所以读取计算属性的时候,执行的是一个内部的 getter
- computed 内部实现了一个惰性的 watcher,在实例化的时候不会去求值,其内部通过 dirty 属性标记计算属性是否需要重新计算。当 computed 依赖的任意属性发生改变,都会通知这个惰性的 watcher,其 dirty 属性设置为 true,当再次读取这个属性的时候,就会去再次求值。
- 计算属性本质上是
- watch 侦听属性
- 侦听属性本质上是
user watcher- 在对 watcher 求值以及执行回调函数的时候,处理一下错误
- 侦听属性适合观察某个值的变化去完成一段复杂的业务逻辑。
- 侦听属性本质上是
- computed 计算属性
-
v-if 🆚 v-show 🆚 v-html
- v-if
- v-if 会调用 addIfCondition,生成 vnode 的时候会忽略对应节点,render 的时候就不会渲染
- Watcher 监听数据变化,然后生成 Vnode 节点,diff 算法对比新旧节点,patch 方法修改真正的 dom 元素
- v-show
- v-show 会生成 vnode 节点,render 的时候也会渲染成真实节点,但是在 render 的过程中会在节点的属性中修改 show 属性值,对应的是 display
- v-html
- 先移除节点下的所有节点,调用 html 方法,通过 addProp 添加 innerHTML 属性,对应于 innerHTML
- v-if
-
自定义 v-model - 自组件定义相同的 model 和 props
// App.vue <p>{{name}}</p> <child-comp v-model="name"> // ChildComp.vue <input type='text' :value="text" @input="$emit('change', $event.target.value)" /> export default { model: { prop: 'text', event: 'change', }, props: { text:{ type: String, defaultValue() { return '' } } } } -
非父子组件传值 EventBus
// Bus.js import Vue from 'vue' export default new Vue() // component import Bus from './Bus.js' Bus.$emit('getTarget', 'some value') Bus.$on('getTarget' val => {}) -
异步组件
components: { componentA: () => import('./component/A') } -
动态组件
<component :is="SOMECOMPONENT' /> -
缓存组件
<keep-alive><comp /></keep-alive>
小程序

本人才疏学浅,文中难免有不妥错误之处,还望同学们批评指正,感激不尽!