vue面试(一)

  1. 1. 谈谈你对vue的理解
    1. 1.1. 声明式框架
    2. 1.2. MVVM模式
    3. 1.3. 采用虚拟dom
    4. 1.4. 区分编译时(打包)和运行时(浏览器)
    5. 1.5. 组件化
  2. 2. SPA
    1. 2.1. 基本概念
    2. 2.2. 解决方案
  3. 3. 虚拟DOM
    1. 3.1. 基本概念
    2. 3.2. VDOM如何生成
    3. 3.3. VDOM 如何做 diff 的?
  4. 4. 组件化
  5. 5. 对响应式数据的理解
    1. 5.1. 如何实现响应式数据
    2. 5.2. vue2处理缺陷
  6. 6. vue中如何检测数组的变化
    1. 6.1. 实现数组劫持
    2. 6.2. 数组的缺点
  7. 7. vue中如何进行依赖收集
    1. 7.1. 依赖收集流程
    2. 7.2. vue3中依赖收集
  8. 8. vue.set方法实现原理
  9. 9. v-show和v-if的优先级
    1. 9.1. 基本概念
    2. 9.2. 如何选择
  10. 10. watch和computed
    1. 10.1. computed
    2. 10.2. watch
    3. 10.3. 其他
  11. 11. ref和reactive区别
    1. 11.1. 基本概念
  12. 12. watch和watchEffect的区别
  13. 13. 如何将template转换成render函数
  14. 14. new Vue()这个过程中做了什么
  15. 15. Vue.observable
  16. 16. v-if和v-for优先级
  17. 17. vue生命周期
    1. 17.1. vue2中生命周期
    2. 17.2. vue3中生命周期
    3. 17.3. 一般在哪一步发送请求

谈谈你对vue的理解

生命式渲染->组件系统->客户端路由->大规模状态管理->构建工具

声明式框架

命令式和声明式的区别:

  • 声明式框架更关注结果,命令时的代码封装到vuejs中,过程靠vuejs实现
  • 声明式代码更加简单,不需要关注实现,按照要求填代码就行
  • 早期jquery的时代编写的代码都是命令式的,命令式框架重要特点就是关注过程

MVVM模式

映射关系的简化(隐藏controller)

采用虚拟dom

传统更新页面,拼接一个完整的字符串innerHTML全部重新渲染,添加虚拟DOM 后,可以比较新旧虚拟节点,找到变化在进行更新。虚拟 DOM 就是一个对象,用来描述真实 DOM

区分编译时(打包)和运行时(浏览器)

  • Vue 的渲染核心就是调用渲染(render)方法将虚拟 DOM 渲染成真实 DOM(缺点就是虚拟 DOM 编写
    麻烦)
  • 专门写个编译时可以将模板编译成虚拟 DOM(在构建的时候进行编译性能更高,不需要再运行的时候进行编译)

组件化

实现高内聚、低耦合、单向数据流

  • 组件化开发能大幅提高应用开发效率、测试性、复用性等:
  • 降低更新范围,只重新渲染变化的组件

SPA

基本概念

  • SPA(single-page application)单页应用,默认情况下我们编写Vue、React 都只有一个htm1 页面,并且提供一个挂载点,最终打包后会再此页面中引入对应的资源。(页面的染全部是由」动态进行渲染的)。切换页面时通过监听路由变化,渲染对应的页面 client side Rendering,客户端渲染 CSR
  • MPA(Multi-page application)多页应用,多个htm1页面。每个页面必须重复加载,js,css等相关资源。(服务端返回完整的 html,同时数据也可以再后端进行获取一并返回“模板引擎”)。多页应用跳转需要整页资源刷新。Server SideRendering,服务器端渲染SSR
SPA MPA
组成 一个主页面和页面组件 多个完整的页面
刷新方式 局部刷新 整页刷新
seo优化 无法实现 整页实现
页面切换 速度快,用户体验良好 切换加载资源,速度慢,用户体验差
维护成本 相对容易 相对复杂
  • 户体验好、快,内容的改变不需要重新加载整个页面,服务端压力小。
  • SPA 应用不利于搜索引擎的抓取
  • 首次渲染速度相对较慢(第一次返回空的 html,需要再次请求首屏数据)白屏时间长。

解决方案

  • 静态页面预渲染(Static Site Generatioh) ssG,在构建时生成完整的 html页面。(就是在打包的时候先将页面放到浏览器中运行一下,将HTML保存起来),仅适合静态页面网站。
  • 变化率不高的网站SSR+CSR 的方式,首屏采用服务端渲染的方式,后续交互采用客户端染方式。NuxtJS

虚拟DOM

基本概念

基本上所有框架都引入了虚拟 DOM 来对真实 DOM 进行抽象,也就是现在大家所熟知的 VNode 和VDOM

  • Virtual DOM 就是用 js 对象来描述真实 DOM,是对真实 DOM 的抽象,由于直接操作 DOM 性能低但是js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过 diff 算法比对差异进行更新 DOM(减少了对真实 DOM 的操作)。
  • 虚拟 DOM 不依赖真实平台环境从而也可以实现跨平台。

VDOM如何生成

  • 在 vue 中我们常常会为组件编写模板-template

  • 这个模板会被编译器编译为渲染函数-render

  • 在接下来的挂载过程中会调用 render 函数,返回的对象就是虚拟 dom

  • 会在后续的 patch 过程中进一步转化为 真实 dom。

VDOM 如何做 diff 的?

  • 挂载过程结束后,会记录第一次生成的 VDOM-oldVnode
  • 当响应式数据发生变化时,将会引起组件重新render,此时就会生成新的VDOM-newVnode
  • 使用 oldVnode 与 newVnode 做 diff操作,将更改的部分应到真实 DOM 上,从而转换为最小量的 dom操作,高效更新视图。

组件化

组件化好处:高内聚、可重用、可组合

  • 组件化开发能大幅提高应用开发效率、测试性、复用性等;
  • 降低更新范围,只重新渲染变化的组件

补充:

  • vue中的每个组件都有一个渲染函数watcher、effect
  • 数据时响应式的,数据变化后会执行watcher或者effect。
  • 组件要合理的划分,如果不拆分组件,那更新的时候整个页面都要重新更新。
  • 如果过分的拆分组件会导致watcher、effec产生过多,也会产生性能浪费

对响应式数据的理解

如何实现响应式数据

数组和对象类型当值变化时如何劫持到。对象内部通过defineReactive方法,使用数组则是通过重写数组方法来实object.defineProperty将属性进行劫持(只会劫持已经存在的属性),现。 多层对象是通过递归来实现劫持。vue3则采用 proxy。

vue2处理缺陷

  • 在 vue2 的时候使用 defineProperty 来进行数据的劫持,需要对属性进行重写添加getter及setter 性能差。
  • 当新增属性和删除属性时无法监控变化。需要通过$set、$delete实现
  • 数组不采用 defineProperty 来进行劫持(浪费性能,对所有索引进行劫持会造成性能浪费)需要对数组单独进行处理。
  • 对于 ES6 中新产生的 Map、Set 这些数据结构不支持。

vue2中响应式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let obj = {name:'hello',age:30}

function defineReactice(target,key,value){
oberver(value)
Object.defineProperty(target,key,value){
get(){
return value
},
set(newValue){
if(value !== newValue){
value = newValue
oberver(newValue)
}
}
}
}

function oberver(data){
if(typeof data !== 'object' || typeft data == null) {
return data
}
for(let key in data){
defineReactive(data,key,data[key])
}
}

observer(obj)
obj.c = 1200
console.log(obj)

vue3中响应式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
letobj = {name:'hello',age:30,n:[1,2,3,4,5]}

let handler = {
get(target,key){
// 收集effect
let temp = target[key]
if(typeof temp === 'object'){
return new Proxy(temp,handler)
}
return temp
},
set(target,key,value){
// 触发effect的更新
target[key] = value
}
}

function reactive(target){
return new Proxy(target,handler)
}

const proxy = reactive(obj)
proxy.name = 100

整理一下vue2使用Obejct.defineProperty()和vue3中使用Proxy代理的区别:

  • Proxy可以劫持的数组的改变,defineProperty 需要变异。可以理解为在数组实例和原型之间,插入了一个新的原型的对象,这个原型方法实现了变异的方法,也就真正地拦截了数组原型上的方法
  • Proxy代理可以劫持对象的改变,defineProperty需要遍历
  • Proxy代理可以劫持对象属性的添加,defineProperty用this.$set来实现
  • Proxy可以直接添加属性

vue中如何检测数组的变化

实现数组劫持

  • 数组考虑性能原因没有用defineProperty对数组的每一项进行拦截,而是选择重写数组(push,shift,pop,splice,unshift,sort,reverse)方法
  • 数组中如果是对象数据类型也会进行递归劫持

数组的缺点

  • 数组的索引和长度变化是无法监控到的

vue中如何进行依赖收集

依赖收集流程

  • 每个属性都拥有自己的 dep属性,存放他所依赖的 watcher,当属性变化后会通知自己对应的 watcher去更新
  • 默认在初始化时会调用 render 函数,此时会触发属性依赖收集 dep.depend
  • 当属性发生修改时会触发watcher更新 dep.notify()

vue3中依赖收集

  • vue3中会通过Map结构将属性和effect映射起来
  • 默认在初始化时会调用render函数,此时会触发属性以来手机track
  • 当属性发生修改时会找到对应的efect列表一次执行trigger

vue.set方法实现原理

vue不允许在已经创建的实例上动态添加新的响应式属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
export function set(taarget,key,val){
//1.是开发环境target 没定义或者是基础类型则报错
if(process.enV.NODE_ENV !=="production" &&(isundef(target)|lisPrimitive(target))){
warn(`Cannot set reactive property on undefined, null, or primitive value:${target}`)
}
//2.如果是数组 vue.set(array,1,100);调用我们重写的splice方法(这样可以更新视图)
if(Array.isArray(target)&& isValidArrayIndex(key)){
target.length=Math.max(target.length,key)
target.splice(key,1,val)
return val;
}
//3.如果是对象本身的属性,则直接添加即可
if(key in target && !(key in object.prototype)){
target[key]= val
return val
}
//4.如果是vue实例 或 根数据data时 报错,(更新_data 无意义)
const ob=target._ob_
if(target._isvuel|(ob && ob.vmcount)){
process.env.NODE_ENV !== "production" &&
warn("Avoid adding reactive properties to a Vue instance or its root $data"+"at runtime - declare it upfront in the data option.")
return val;
}
//5,如果不是响应式的也不需要将其定义成响应式属性
if(!ob){
target[key]= val
return val
}
//6.将属性定义成响应式的
defineReactive(ob.value, key, val)
// 通知视图更新
ob.dep.notify();
return val;
}

当我们选择新增属性时,可以考虑使用对象合并的方式实现

1
this.info = {...this.info,{newPorperty1:1,newProerty2:2,...}}

v-show和v-if的优先级

基本概念

  • v-if如果条件不成立不会渲染当前指令所在节点的dom元素
  • v-show知识切换当前dom的限时或者隐藏

如何选择

  • v-if可以阻断内部代码是否执行,如果条件不成立不会执行内部逻辑
  • 如果页面逻辑在第一次加载时已经背确认后续不会频繁更改则采用v-if

watch和computed

vue2中有三种watcher(渲染watcher、计算属性watcher、用户watcher)

vue3中有三种effect(渲染effect、计算属性effect、用户effect)

computed

  • 计算属性仅当用户取值时才会执行对应的方法
  • computed属性时具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会重新执行
  • 计算属性可以简化模板中复杂表达式
  • 计算属性中不支持异步逻辑
  • computed属性可以在模板中使用

计算属性会创建一个watcher,这个watcher(lazy:true)不会立即执行

通过Object.defineProperty将计算属性定义到实例上

当用户取值时会触发getter,拿到计算属性对应的watcher,看dirty是否为true,如果为true则求值

若依赖的值没有发生变化,则采用缓存

watch

watch时监控值的变化,当值发生变化时调用对应的回调函数。经常用于监控某个值的变化,进行一些操作。(异步要注意竞态问题)

vue3提供了onCleanup函数,让用户更加方便使用也解决了清理问题。

其他

模板表达式,写的太长、不好维护

方法来实现:没有缓存,多次调用

ref和reactive区别

基本概念

ref和reactive时vue3数据响应式中非常重要的两个概念

  • reactive用于处理对象类型的数据响应式,底层采用的是new Proxy()
  • ref通常用于处理单值的响应式,ref主要解决原始值的响应式问题。底层采用的是Object.defineProperty()实现

reactive中如果存一个ref响应式,那么使用时vue会自动为我们拆包,使用时不必再加上value

watch和watchEffect的区别

  • watchEffect 立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。
  • watch 侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 底层
cosnt effect = new ReactiveEffect(getter.scheduler);
effect.run();

// getter函数
watchEffect(()=>{
app.innnerHTML = state.name; //数据变化后,会调用scheduler内部会再次触发effect.run()重新运行getter
});

// 1.getter函数 2.cb函数
watch(()=>state.name,// 数据变化后,会调用scheduler,内部会调用cb
(newVal,oldVal)=>{}
)

如何将template转换成render函数

Vue 中含有模版编译的功能,它的主要作用是将用户编写的 template 编译为is 中可执行的 render 函数。

  1. 将 template 模板转换成ast抽象语法树-parserHTML
  2. 对静态语法做静态标记-markup diff来做优化的 静态节点跳过diff操作
  3. 重新生成代码-codeGen

new Vue()这个过程中做了什么

  • 在new Vue 的时候 内部会进行初始化操作。
  • 内部会初始化组件绑定的事件,初始化组件的父子关系(lifecycle)$parent$children $root
  • 初始化响应式数据 data、computed、props、watch、method。同时也初始化了provide和inject方法。内部会对数据进行劫持 对象采用 defineProperty数组采用方法重写。
  • 在看一下用户是否传入了el属性和template 或者render。render 的优先级更高,如果用户写的是template,会做模板编译(三部曲)。最终就拿到了render函数
  • 内部挂载的时候会产生一个 watcher,会调用render 函数会触发依赖收集。内部还会给所有的响应式数据增加 dep 属性,让属性记录当前的 watcher (用户后续修改的时候可以触发 watcher 重新渲染)
  • vue 更新的时候采用虚拟 DOM 的方式进行 diff 算法更新。

可结合vue2文档中生命周期图片加深理解

Vue.observable

vue2.6中新增的api,可以将一个普通对象变成响应式对象。

在非父子组件通信时,可以使用eventBus或者使用状态管理工具,但是功能不复杂的时候我们可以考虑使
用vue.observable

在vue3中此api完全可以使用reactive替代,不再使用此api。

v-if和v-for优先级

在vue2中,v-if和v-for在统一标签内使用,v-for优先级会更高。编译结果是优先循环遍历,然后在回调的返回值中用三元判断替代if。

在vue3中,v-if优先级高于v-for,编译后的结果是先判断是否满足条件,满足后再循环执行。

但实际使用时,应当避免v-for和v-if连用。

vue生命周期

vue2中生命周期

主要生命周期有:创建前后、挂载前后、更新前后,销毁前后。

  • beforeCreate 初始化父子关系及事件,数据观测(data observer)之前被调用。用此方法一般编写插件的时候会用到。
  • created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法等,但是这里没有$el,一般也不咋用。
  • beforeMount 在挂载开始之前被调用:相关的render函数首次被调用。
  • mounted el被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。可以用于获取 DOM 元素。
  • beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。此时修改数据不会再次出发更新方法。
  • updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
  • beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
  • keep-alive (activated和deactivated)

vue3中生命周期

声明周期v2 生命周期v3 描述
beforeCreate beforeCreate 组件实例被创建之初
created created 组件实例已经完全创建
beforeMount beforeMount 组件挂载之前
mounted mounted 组件挂载到实例上去之后
beforeUpdate beforeUpdate 组件数据发生变化,更新之前
updated updated 数据数据更新之后
beforeDestroy beforeUnmount 组件实例销毁之前
destroyed unmounted 组件实例销毁之后
activated activated keep-alive 缓存的组件激活时
deactivated deactivated keep-alive 缓存的组件停用时调用
errorCaptured errorCaptured 捕获一个来自子孙组件的错误时被调用
- renderTracked Dev 调试钩子,响应式依赖被收集时调用
- renderTriggered Dev 调试钩子,响应式依赖被触发时调用
- serverPrefetch ssronly,组件实例在服务器上被渲染前调用

Vue3 中新增了,组合式 API:生命周期钩子,但是不存在 onBeforeCreate 和onCreated 钩子

一般在哪一步发送请求

因为请求是异步的,生命周期钩子是同步执行的,所以根据业务安排请求在哪一步,一般在mounted中