Vue面试笔记
💡VUE
main.js 的运行过程
- 引入Vue对象(相当于引入vue.js)
- 引入App.vue 对象
- 实例化vue
- 渲染App页面
- 把页面渲染到 index.html 里,手动挂载到id为app的dom中。
data 为什么是一个函数
- 如果data是一个对象,那么如果组件多次使用,实际上是同用一个数据。
- 源码中是通过
initData返回数据,那么每个组件使用的数据是独立的,都有自己的作用域。
MVVM模式
MVC的改进,MVVM是双向的,数据变化更新视图,视图更新会自动同步据。
==优点==:
- 耦合性低,view 和 model 完全分离
- 维护性高
- 双向绑定
- 减少了 DOM 操作。
MVC 模式
将==视图==和==数据==进行分离,让代码更好维护,耦合性变低,提高复用性。
- 设计
- Model:数据层(存数据,改数据)
- View:视图层(页面展示,DOM操作)
- Controller:控制视图层和数据层的关联,监听DOM事件
vue语法中的命令
- 插值表达式:{ { } }
在 dom 中直接插入 vue 数据变量,数据变量在 js 中 data 函数里面声明
- 给标签属性设置变量的值:v-bind / :
- v-bind : 属性名 = “变量”
- 给标签绑定事件 : v-on / @click
- v-on : 事件名 = “执行的代码”
- v-on:事件名 = “函数名”
- v-on:事件名 = “函数名(实参)”
事件中的 this 指向 exports default { } 中的变量
-
事件修饰符:v-on : 事件名.修饰符 = ‘’函数‘’
- .prevent 阻止默认行为
- .stop 阻止冒泡
-
.once 程序运行期间,只触发一次事件函数
v-on : keyup.enter = ''函数'' 监测回车 - v-on :keyup.esc = ‘’函数‘’ 监测返回
-
表单value和vue变量的双向绑定:v-model : ‘‘vue变量’’
- 下拉菜单 ,v-model绑定在select上
- 复选框,v-model
- (内容为非数组):关联的是复选框的checked属性
- (内容为数组):关联的是复选框的value值
- v-model.修饰符 = “”
作用:实现数据的双向绑定,以及组件之间的通信 原理:input事件和 :value的语法糖
<input v-model="val">
//相当于
<input :value="val" @input = "val = $event.target.value>
场景:
- 在表单中使用。
- 组件通信:当父组件传递给子组件,且子组件要修改数据传递给父组件时,用 v-model 进行简化。
- 缺点:一个组件只能使用一次v-model。
<child v-model="color"> //传递color给子组件
//相当于
<child :value = "color" @input = "color = $event">
<div @click = "$emit('input','数据')">
props:['value']
//也可以自定义v-model 的属性名和事件名
model:{
prop:'自定义属性名',
event:'自定义事件名'
}
- 为了解决一个组件只能使用一次v-model的缺点,引入了.sync修饰符
- sync 修饰符
- 传递单个数据(.sync=“数据 ”)
<child :color.sync = "color">
//相当于
<child :属性名=“数据” @update:属性名 = “数据 = $event”>
- 传递一个对象(v-bind.sync=”对象”)
- 可以将对象的每个属性打散开,分别生成对应的
//父组件
@update:属性名1 = “对象.属性1 = 新值”
@update:属性名2 = “对象.属性2 = 新值”
//子组件
$emit('update:属性名1',新值);
$emit('update:属性名2',新值);
- v-if 和 v-for 为什么避免同时使用
- vue2中:v-for的优先级高于v-if的优先级,会先通过循环创建虚拟DOM,再通过v-if进行移除
- 解决:
- v-if 写到外层标签
- 先通过计算属性将数据计算好
- 解决:
- vue3中:v-if优先级高
computed 和 watch的区别
- computed:
- 计算属性,通过其他变量得来的的另一个属性。
- 只在依赖发生改变时候才会重新求值,需要 return。
- 只会计算一次,只要数据不变,后续数据使用的是缓存。
- 不能处理异步,因为如果是异步操作 return 是没有意义的。
- watch:
- 监听一个特定的值,值发生变化时执行特定逻辑。
- 可以监听 props、data、computed、$route
- 当监听的数据发生变化,对应函数会重新执行。
- 如果监听一个对象需要开启深度监听,
deep:true。 - 可以处理异步。
🔺vuex的同步异步流程
- 同步流程
- 通过 $store 的 commit 触发 mutations 中的事件 ,修改 state 中的 数据。
- 异步流程
- 通过 $store 的 dispatch 触发 actions 中的事件,再用 context中的commit 提交给 mutations 从而修改 state 中的数据。
🔺简单谈谈vuex
- vuex就是基于vue的一个状态管理工具
- 它主要用于实现数据的共享,同时数据也是响应式的,因此它就方便了各个组件之间共享数据的操作,也有利于数据的维护的管理。
- 它有五个主要的状态:
- state 是用来存储和定义用来共享的数据的属性,可以用$store 或者是 辅助函数来拿到state的数据,因为辅助函数本质上会返回一个对象,可以把vuex中的数据映射过来,在使用的时候必须解构出来。
- mutations 是 用来修改state 数据的唯一来源,但是也可以直接修改state 中的数据,但是不建议,因为一旦数据出现错误会造成state中的数据混乱,它一般是用来处理同步流程,通过commit 提交给mutations 从而修改state 中的数据,如果用它处理异步流程,那么可能造成调试工具的不准确。
- actions 主要用来处理异步流程,通过dispatch 传递给 actions 在调用 mutations 来修改数据,一般在工作中,为了避免操作的繁琐,基本都把同步异步流程都放到actions中进行处理,而把数据的修改提交到mutations中。
- getters 主要把state中的数据新建派生处理,即根据state中的数据生成另一种数据。
- modules 主要是把vuex数据模块化,方便管理和维护,每个模块里都有对应模块的 state/mutations/actions/getters 属性,如果没有开启命名空间,模块中的 mutations/actions中的方法和全局中的并无差别,开启命名空间之后,使用各自模块的方法和数据都必须加上模块名,或者是根据createNamespacedHelper生成对应命名空间的辅助函数,它能拿到当前命名空间的所有辅助函数,从而实现对当前命名空间数据或方法的使用。
- vuex的缺点就是 他其中的数据不支持持久化操作,当然我们可以通过添加本地存储的方式来实现手动持久化,或者是通过插件vuex-persistedstate 来实现自动的数据持久化操作,其中可以对指定模块进行数据持久化。在
plugins配置项中的createPersistedState()对单独的模块进行配置 - 在日常的业务中,一般的用户信息以及用户唯一标识token都放到vuex中进行存储,以及一些多组件之间需要频繁传递的参数一般也放到vuex中进行存储。
v-if 和 v-show 的区别
-
v-if / v-show 都会控制元素的显示和隐藏
-
v-show 相当于给标签添加了样式 display:none ,
优点:不会频繁创建或者删除 dom。
缺点:首次渲染,如果为false,也会创建元素依旧在,更高的初始渲染开销。
场景:如果需要频繁的切换,可以用v-show。
-
v-if 是直接从 DOM 中移除或者添加元素。
优点:懒渲染,如果有此渲染是false ,那么久不会创建元素,如果是组件,可以重新触发生命周期。 缺点:会频繁的删除和创建dom元素,增加了性能负担,v-if有更高的切换开销 应用场景:如果元素很少改变,或者组件需要重新触发生命周期,或者首次元素为隐藏。
route 和 router 的区别
- route 是当前路由信息,可以获取到当前路由地址(path)参数(params,query)等等。$ route是一个跳转的路由对象(路由信息对象),每一个路由都会有一个$route对象,是一个局部的对象。
- router 是全局路由(VueRouter)实例对象,可以通过 router 进行路由的跳转后退等等。他包含了所有的路由,包括路由的跳转方法,钩子函数,也包含一些子对象(例如history)
Vue.use(install(){},2,3)原理
- 作用:用来全局注册组件。
- 里面的参数1可以有两种形式
- 如果传入的参数是一个对象的话,那么这个对象必须提供一个install()方法,其方法的参数就是Vue。
- 如果传入的参数是一个回调函数的话,那么这个函数的参数就是Vue对象
- 如果我们想要全局注册组件,其实用到的是ues()方法里的install里面的Vue.component注册全局组件或者给Vue.prototype手动添加方法的逻辑。
- 剩余参数,就作为了install方法的剩余的参数。
请求优化
- 通过定义一个环境变量(不同环境下定义一个同名数据,在不同环境下值不同)来实现工程化的切换生成/测试/开发所用到的基准地址。
vue封装组件的思想
- 准备结构:结构考虑复用灵活,一般使用插槽允许自定义
- 组件的样式:考虑样式支持自定义,一般使用属性传值(有可能是样式,通过类名)
- 组件的数据: 通过数据传递
- 逻辑,事件,:例如弹框组件,点击遮罩弹框关闭,用户使用组件的时候,也需要监听到点击遮罩的行为,用户想进行自定义的逻辑,
$nextTick
作用:用来数据更新后,开始触发,更新视图。
- 如数数据更新之后,我们是立马拿不到,因为dom的更新是异步的,可以用$nextTick 获取最新更新后的视图。
- 因为本质上它是一个可宏可微的任务,在不传回调的时候默认使用的promise.then是微任务,在传回调的时候默认内部是定时器,是宏任务。
场景:一般的话,用可以用在,想要在create()中拿到dom的时候。
vue生命周期
- 创建阶段
- beforeCreate() : vue实例开始初始化,但是拿不到实例里面的方法和数据,此时方法和数据并没有添加到实例中。
- created() : vue实例初始化完成,可以访问到 实例的数据和方法。
- 挂载阶段
- beforeMount():dom 未挂载,不可以操作dom
- mounted():dom 已挂载,此时可以操作dom
- 更新阶段
- beforeUpdate():数据更新,但是视图没有更新,此时是不同步的。
- updateed():数据更新,视图也更新,二者同步。此时虚拟dom变为新的dom。
- 销毁阶段
- beforeDestory():实例开始销毁,清除挂载在 window 上的行为定时器或者是事件。vue实例上绑定的东西会解绑,解除事件监听。
- destory():实例销毁后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁
- 父子生命周期
- 创建阶段和挂载阶段
- 父beforeCreate()— 父created()— 父beforeMount()— 子beforeCreated()— 子created()— 子beforeMount()— 子mounted()—- 父mounted()
- 更新阶段
- 如果父级更新的数据不涉及子组件:父beforeUpdate()—父update()
- 如果父级更新的数据涉及子组件:父beforeUpdate()—子beforeUpdate()—子update()–父update()
- 销毁阶段
- 父beforeDestory()—子beforeDestory()—子destoryed()–父destoryed()
- 创建阶段和挂载阶段
虚拟DOM
- 本质:是一个js对象,用来描述真的DOM。
- 优点:可以进行高效更新,跨平台
- 实现高效更新:
- 初始化:根据数据和模板生成一个虚拟dom树。
- 当数据发生变化:根据新的数据生成一个新的虚拟dom树。
- 对比两个新旧虚拟dom:diff算法
- 采用同级比较,深度优先的方法,进行双指针算法处理,新旧dom都分别有两个指向开始和结束的指针。
- 对比之后,元素不同:对元素删除重建
- 元素相同,属性不同:元素复用,属性更新
- 用 v-for 遍历数据:
- 无 key :观察数据的变化是否影响到顺序
- 顺序发生变化:那么就会影响到性能,不会对元素进行复用
- 顺序没发生变化:复用相同元素,更新不同元素
- 有 key :会根据 key 值来判断元素是否更新,如果可以找到相同的 key 则会复用,如果找不到相同的 key 值,那么就只更新这个 key 值的元素。
- key 不推荐使用 index :一旦在对元素进行逆序的删除或者添加的话,那么所有元素的索引就会改变,会产生没必要的 dom 更新或者是错误的更新,影响性能 。
- 无 key :观察数据的变化是否影响到顺序
Mixins
1. 概念
- 将组件中的逻辑功能进行复用,复用部分可以提取到一个 js 文件中,然后在需要使用复用逻辑的地方通过 mixins 选项将该文件中暴露的对象进行混入即可。
2. 优先级
- 生命周期:组件和混入的都会调用(混入的优先)
- data数据:进行合并,若有冲突,以组件的为主,mixins中的冲突数据会被覆盖。
- methods、components和directives,将被合并为同一个对象,两个对象键名冲突时,以组件对象的键值为主。
❗数据响应式
1. 概念
- 数据发生变化,使用数据的地方就会进行同步更新。
2. 响应式核心
Object.defineProperty:可对对象进行属性定义内部有:
- set(),数据变化会触发
- get(),数据读取会触发
3. 劫持数据
- 要通过一个数据进行周转,如果还用原数据进行set或者get的话会导致堆栈溢出。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>Document</title>
</head>
<body>
<div>
<p id="name"></p>
<p id="age"></p>
</div>
<script>
// 定义数据
const data = {
name: '张三',
age: 19
}
// 同步视图的方法
function setView() {
document.getElementById('name').innerHTML = data.name
document.getElementById('age').innerHTML = data.age
}
setView()
// 利用循环对数据进行监听
Object.keys(data).forEach(key => {
// 利用外部数据进行数据的读写控制
let getValue = data[key]
Object.defineProperty(data, key, {
// 读数据
get() {
return getValue
},
// 写数据
set(val) {
getValue = val
setView()
}
})
})
</script>
</body>
</html>
Object.defineProperty一次只能劫持一个数据,所以源码中需要对数据进行循环遍历劫持,类似于递归的方式对所有的数据进行劫持。- 对象新增的数据是无法劫持到的,因为新增数据的操作是在本次数据劫持之后,新增属性并没有被劫持。所以对象新增的数据也就不是响应式的了,可以通过
this.$set()添加响应式属性来解决。 - 通过数组的下标修改数据,通过
Object.defineProperty是可以劫持到的,但是vue中因为性能和用户体验的关系,取消了vue中检测数据下标变化。 - 但是数组的7个方法是响应式的原因是因为vue中对7个方法进行重写了,并不是完全重写,它是利用了切面编程的思想,对方法进行封装以及逻辑的处理,最终还是调用了原始的方法。
4. 观察者模式
另外响应式原理还需要配合观察者模式。
- 是一对多的数据依赖关系和发布订阅模式的不同在于,没有调度中心,被观察者和观察者直接通信,观察者内部会有 update 方法进行消息更新,被观察者会有 add 收集观察者,通过notify方法通知所有观察者,调用 update 方法更新观察者收到的信息。
- vue 的源码中,通过 get 进行依赖的收集,会给每个数据添加一个 watcher 观察者(每一个watcher)对应的就是一个组件。通过 set 在数据变化时通知观察者(组件),然后生成虚拟DOM,进行新旧DOM对比,最后渲染页面。

V2 和 V3 的区别
- api
- v2: options API(选项式API):优点:对于新手容易,按照固定选项实现功能缺点: 对于代码的阅读\维护\复用不是很友好,v2中也可以通过Mixins实现逻辑的复用
- v3: composition API(组合式API)优点: 功能代码组合在一起
- 使用
- v2: 配合vuex,对与ts的支持不是很好,不允许使用多个根节点
- v3: 配合pina,底层就使用ts重构的,对ts支持很好,多个根节点
- 性能
- v3的性能会比v2快1.2_2倍:
- v3采用了动态标记,给动态节点打上标记,diff的过程只对比动态节点,忽略静态节点
- 虚拟dom的创建:v2中,数据发生变化,会将模板进行重编译,生成所有的虚拟dom,v3中数据发生变化,看该虚拟dom是否参与更新,如果参与更新,创建新的虚拟dom,如果不参与更新,直接复用上一次的虚拟dom
- 事件函数的缓存: 事件函数每次在模板编译的时候会进行缓存
- v3的tree shaking:实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的,v3的打包比v2的小
- 数据劫持
- v2:Object.defineProperty
- v3: Proxy
keep-alive
- created 初始化两个对象分别缓存VNode(虚拟DOM)和VNode对应的键集合
- 在mounted这个钩子中对include和exclude参数进行监听,然后实时地更新(删除)this.cache对象数据。pruneCache函数的核心也是去调用pruneCacheEntry
- destroyed 删除this.cache中缓存的VNode实例。我们留意到,这不是简单地将this.cache置为null,而是遍历调用pruneCacheEntry函数删除。