Vue面试题整理
1. 谈谈你对Vue的理解?
官方: Vue 是一套用于构建用户界面的渐进式框架,Vue 的核心库只关注视图层
1.1 声明式框架
Vue 的核心特点,用起来简单。那我们就有必要知道命令式和声明式的区别!
- 早在JQ 的时代编写的代码都是命令式的,命令式框架重要特点就是关注过程
- 声明式框架更加关注结果
命令式的代码封装到了 Vuejs 中,过程靠 vuejs 来实现声明式代码更加简单,不需要关注实现,按照要求填代码就可以(给上原材料就出结果)
- 命令式编程:
let numbers = [1,2,3,4,5]
let total = 0
for(let i = 0; i < numbers.length; i++){
total += numbers[i] //关注了过程
}
console.log(total)
-声明式编程:
let total2 = numbers.reduce(function (memo,current) {
return memo + current
},0)
console.log(total2)
1.2 MVVM模式
说起MVVM
,就要知道另一个东东那就是 MVC
。为什么要有这些模式呢?
目的:职责划分、分层管理.
对于前端而言就是如何将数据同步到页面上,也是借鉴后端思想。
- MVC模式:
Backbone
+underscore
+jquery
对于前端而言,数据变化无法同步到视图中。需要将逻辑聚拢在
controller
层。 - MVVM 模式:映射关系的简化(隐藏
controller
)虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm(ViewModel的缩写) 这个变量名表示 Vue 实例。
1.3 采用虚拟DOM
传统更新页面,拼接一个完整的字符串 innerHTML
全部重新渲染,添加虚拟 DOM 后,可以比较新旧虚拟节点,找到变化在进行更新。虚拟 DOM 就是一个对象,用来描述真实 DOM 的
https://github.com/vuejs/vue/blob/main/src/core/vdom/vnode.ts
1.4 区分编译时(打包)和运行(浏览器)时
- Vue 的渲染核心就是调用渲染(
render
)方法将虚拟 DOM 渲染成真实 DOM(缺点就是虚拟 DOM 编写麻烦) - 专门写个编译时可以将模板编译成虚拟 DOM(在构建的时候进行编译性能更高,不需要再运行的时候进行编译)
1.5 组件化
实现高内聚、低耦合、单向数据流
- 组件化开发能大幅提高应用开发效率、测试性、复用性等
- 降低更新范围,只重新渲染变化的组件
2. 谈谈你对 SPA 的理解?
2.1 理解基本概念
- SPA(single-page application)单页应用,默认情况下我们编写 Vue、React都只有一个htm1 页面,并且提供一个挂载点,最终打包后会再此页面中引入对应的资源。(页面的渲染全部是由IS 动态进行渲染的)。切换页面时通过监听路由变化,渲染对应的页面 Clientside Rendering,客户端渲染
CSR
- MPA(Multi-page application)多页应用,多个html页面。每个页面必须重复加载,js,css 等相关资源。(服务端返回完整的 html,同时数据也可以再后端进行获取一并返回“模板引擎”)。多页应用跳转需要整页资源刷新。Server side Rendering,服务器端渲染
SSR
如何分清在哪渲染:HTML是在前端动态生成的“客户端渲染",在服务端处理好并返回的是“服务端渲染”。
2.2 优缺点
单页面应用(SPA) | 多页面应用(MPA) | |
---|---|---|
组成 | 一个主页面和页面组件 | 多个完整的页面 |
刷新方式 | 局部刷新 | 整页刷新 |
SEO 搜索引擎优化 | 无法实现 | 容易实现 |
页面切换 | 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差 |
维护成本 | 相对容易 | 相对复杂 |
- 用户体验好、快,内容的改变不需要重新加载整个页面,服务端压力小。
- SPA 应用不利于搜索引擎的抓取。
- 首次渲染速度相对较慢(第一次返回空的 html,需要再次请求首屏数据)白屏时间长。
2.3 解决方案
- 静态页面预渲染(Static Site Generation)
SSG
,在构建时生成完整的 html 页面。(就是在打包的时候,先将页面放到浏览器中运行一下,将HTML保存起来),仅适合静态页面网站。变化率不高的网站 SSR
+CSR
的方式,首屏采用服务端染的方式,后续交互采用客户端染方式。例如:Nuxtjs
3. Vue 为什么需要虚拟 DOM?
3.1 基本概念
基本上所有框架都引入了虚拟 DOM 来对真实 DOM 进行抽象,也就是现在大家所熟知的 VNode 和 VDOM
- Virtual DOM 就是用 js 对象来描述真实 DOM,是对真实 DOM 的抽象,由于直接操作 DOM 性能低但是 js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过 diff 算法比对差异进行更新 DOM(减少了对真实 DOM 的操作)。
- 虚拟 DOM 不依赖真实平台环境从而也可以实现跨平台。
3.2 VDOM 是如何生成的?
- 在 vue 中我们常常会为组件编写模板 -
template
- 这个模板会被编译器编译为渲染函数 -
render
- 在接下来的挂载过程中会调用
render
函数,返回的对象就是虚拟 dom。 - 会在后续的
patch
过程中进一步转化为 真实 dom。
3.3 VDOM 如何做 diff 的?
- 挂载过程结束后,会记录第一次生成的
VDOM-oldVnode
- 当响应式数据发生变化时,将会引起组件重新
render
,此时就会生成新的VDOM-newVnode
- 使用
oldVnode
与newnode
做diff
操作,将更改的部分应到真实 DOM 上,从而转换为最小量的 dom 操作,高效更新视图。
4. 谈一谈对 Vue 组件化的理解
webcomponent 组件化的核心组成:板、属性、事件、插、生命周期。
组件化好处: 高内聚、可重用、可组合
- 组件化开发能大幅提高应用开发效率、测试性、复用性等,
- 降低更新范围,只重新渲染变化的组件;
4.1 伴随组件化的其它问题
- Vue 中的每个组件都有一个渲染函数 watcher、effect。
- 数据是响应式的,数据变化后会执行watcher或者effect。
- 组件要合理的划分,如果不拆分组件,那更新的时候整个页面都要重新更新。
- 如果过分的拆分组件会导致watcher、effect产生过多也会造成性能浪费。
5. 既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
Vue 内部设计原因导致,Vue 设计的是每个组件一个 watcher (渲染 watcher),没有采用一个属性对应一个 watcher。这样会导致大量 watcher 的产生而且浪费内存,如果粒度过低也无法精准检测变化。所以采用 diff 算法+组件级 watcher。
6. 请说一下你对响应式数据的理解?
6.1 如何实现响应式数据
数组和对象类型当值变化时如何劫持到。对象内部通过 defineReactive
方法,使用object.defineProperty
将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。多层对象是通过递归来实现劫持。Vue3 则采用 proxy
6.2 Vue2 处理缺陷
- 在 Vue2 的时候使用
defineProperty
来进行数据的劫持,需要对属性进行重写添加getter
及setter
性能差。 - 当新增属性和删除属性时无法监控变化。需要通过
$set
、$delete
实现 - 数组不采用
defineProperty
来进行劫持(浪费性能,对所有索引进行劫持会造成性能浪费)需要对数组单独进行处理。 - 对于 ES6 中新产生的 Map、Set 这些数据结构不支持。
6.3 Vue2 与 Vue3 实现对比
function defineReactive(target,key,value{
observer(value);
Object.defineProperty(target,key,{
get(){
return value;
},
set(newvalue){
if(value !== newvalue){
value = newalue;
observer(newvalue)
}
}
})
}
function observer(data){
if(typeof data !=='object'){
return data
}
for(let key in data){
defineReactive(data,key,data[key]);
}
}
let handler ={
get(target, key){
if(typeof target[key]==="object"){
return new Proxy(target[key], handler);
}
return Reflect.get(target, key);
},
set(target, key,value){
let oldvalue = target[key];
if(oldvalue !== value){
return Reflect.set(target, key,value);
}
return true;
},
};
let proxy =new Proxy(obj, handler);
7. Vue 中如何检测数组变化?
7.1 实现数组劫持
- 数组考虑性能原因没有用
defineProperty
对数组的每一项进行拦截,而是选择重写数组(push
,shift
,pop
,splice
,unshift
,sort
,reverse
)方法。 - 数组中如果是对象数据类型也会进行递归劫持
7.2 数组的缺点
- 数组的索引和长度变化是无法监控到的