keep-alive 组件有什么作用?
keep-alive 是 vue 的内置组件,一般情况下,组件进行切换的时候,默认是会进行销毁的,如果我们想在某个组件切换后不进行销毁,而是保存之前的状态,那么就可以利用 keep-alive 来实现。
keep-alive是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
场景:
用户在某个列表页面选择筛选条件过滤出一份数据列表,由列表页面进入数据详情页面,再返回该列表页面,
我们希望:列表页面可以保留用户的筛选(或选中)状态。
keep-alive就是用来解决这种场景。当然keep-alive不仅仅是能够保存页面/组件的状态这么简单,它还可以避免组件反复创建和渲染,有效提升系统性能。
总的来说,keep-alive用于保存组件的渲染状态。
include 和exclude 值为字符串或正则表达式匹配的组件 name 会不会被缓存。
//router.js
{
path: '/',
name: 'xxx',
component: ()=>import('../src/views/xxx.vue'),
meta:{
keepAlive: true // 需要被缓存
}
},
(exclude优先)
有两个独立的生命周期actived 和 deactived,
使用 keep-alive 包裹的组件在切换时不会被销毁,而是缓存到内存中并执行 deactived 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
vue生命周期
生命周期函数中的this指向是vm 或 组件实例对象。
关于VueComponent
vue 组件中 data 必须是一个函数?
如果 data 是一个对象,当复用组件时,因为 data 都会指向同一个引用类型地址,其中一个组件的 data 一旦发生修改,其他组件中的 data 也会被修改。
如果 data 是一个返回对象的函数,因为每次重用组件时返回的都是一个新对象,引用地址不同,不会出现如上问题。
Vue 中 v-if 和 v-show 有什么区别?
v-if 在进行切换时,会直接对标签进行创建或销毁,不显示的标签不会加载在 DOM 树中。 v-show 在进行切换时,会对标签的 display 属性进行切换,通过 display 不显示来隐藏元素。
一般来说,v-if 的性能开销会比 v-show 大,切换频繁的标签更适合使用 v-show。
Vue 中 computed 和 watch 有什么区别?
computed:
支持缓存,只有依赖数据发生变化时,才会重新进行计算函数;
计算属性内不支持异步操作;
计算属性的函数中都有一个 get和set
计算属性是自动监听依赖值的变化,从而动态返回内容。
get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
watch:
不支持缓存,只要数据发生变化,就会执行侦听函数;
侦听属性内支持异步操作,
侦听属性的值可以是一个对象,接收 handler 回调,deep,immediate
监听是一个过程,在监听的值变化时,可以在里面操作一些方法,compute不可以,例如加一个定时器。
Vue-router 路由有哪些模式?
hash 模式
后面的 hash 值的变化,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变化会触发 hashchange 事件。
history 模式
利用了 HTML5 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们 提供了对历史记录进行修改的功能。
vue的 $forceUpdate 和$set
$forceUpdate(用来全局强制刷新,性能消耗高)
迫使VUE实例重新渲染。
注意:它仅仅影响实例本身和插入插槽内容的子组件,而不是所有的子组件。
使用场景:
1.路由切换时,页面数据比较复杂
2.改变多维数组里面的数据
3.data中的变量为数组或对象,我们直接去给某个对象或数组添加属性,页面是识别不到的
原理
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
实例需要重新渲染,是在依赖发生变化的时候会通知watcher,然后通知watcher来调用update方法.
this.set(object, index, new) ,this.set()方法是vue自带的,可对数组和对象进行赋值,并触发监听的方法。(用来指向性强制刷新,性能消耗低)
注意项: 注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
this.$set(this.$data, 'age', 24) //不能给vue实例的根数据对象设置,会报错
向响应式的对象中添加一个属性,并确保这个新的属性同样也是响应式的,同时触发相应视图的更新。$set必须用于向响应式的对象中添加新的属性,因为Vue无法探测普通的新增属性。
diff算法
什么是虚拟DOM
一个用来表示真实DOM的对象
虚拟DOM比真实DOM快,这句话不对。
虚拟DOM算法操作真实DOM,性能高于直接操作真实DOM。
虚拟DOM和虚拟DOM算法是两种概念。
虚拟DOM算法 = 虚拟DOM + Diff算法
什么是Diff算法
总结:Diff算法是一种对比算法。对比旧虚拟DOM和新虚拟DOM,找出更改了的虚拟节点,并只更新这个虚拟节点所对应的真实节点,不更新其他数据没发生改变的节点,实现精准地更新真实DOM,进而提高效率。
使用虚拟DOM算法的损耗计算:
总损耗 = 虚拟DOM增删改+(与Diff算法效率有关)真实DOM差异增删改+(较少的节点)排版与重绘
直接操作真实DOM的损耗计算:
总损耗 = 真实DOM完全增删改+(可能较多的节点)排版与重绘
Diff算法的原理
新旧虚拟DOM对比的时候,Diff算法比较只会在同层级进行, 不会跨层级比较。
Diff对比流程
当数据改变时,触发setter,并通过Dep.notify去通知所有订阅者Watcher,订阅者们就会调用patch方法,给真实DOM打补丁,更新相应的视图。
patch方法
作用:对比当前同层的虚拟节点是否为同一种类型的标签
是:继续执行patchVnode方法进行深层比对
否:没必要对比了,直接整个节点替换成新虚拟节点
function patch(oldVnode, newVnode) {
// 比较是否为一个类型的节点
if (sameVnode(oldVnode, newVnode)) {
// 是:继续进行深层比较
patchVnode(oldVnode, newVnode)
} else {
// 否
const oldEl = oldVnode.el // 旧虚拟节点的真实DOM节点
const parentEle = api.parentNode(oldEl) // 获取父节点
createEle(newVnode) // 创建新虚拟节点对应的真实DOM节点
if (parentEle !== null) {
api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素
api.removeChild(parentEle, oldVnode.el) // 移除以前的旧元素节点
// 设置null,释放内存
oldVnode = null
}
}
return newVnode
}
sameVnode方法
patch关键的一步就是sameVnode方法判断是否为同一类型节点
比较key,标签名,是否都为注释节点,是否定义了data,标签为input时,type必须相同
function sameVnode(oldVnode, newVnode) {
return (
oldVnode.key === newVnode.key && // key值是否一样
oldVnode.tagName === newVnode.tagName && // 标签名是否一样
oldVnode.isComment === newVnode.isComment && // 是否都为注释节点
isDef(oldVnode.data) === isDef(newVnode.data) && // 是否都定义了data
sameInputType(oldVnode, newVnode) // 当标签为input时,type必须是否相同
)
}
patchVnode方法
找到对应的真实DOM,称为el
判断newVnode和oldVnode是否指向同一个对象,如果是,那么直接return
如果他们都有文本节点并且不相等,那么将el的文本节点设置为newVnode的文本节点。
如果oldVnode有子节点而newVnode没有,则删除el的子节点
如果oldVnode没有子节点而newVnode有,则将newVnode的子节点真实化之后添加到el
如果两者都有子节点,则执行updateChildren函数比较子节点,这一步很重要
function patchVnode(oldVnode, newVnode) {
const el = newVnode.el = oldVnode.el // 获取真实DOM对象
// 获取新旧虚拟节点的子节点数组
const oldCh = oldVnode.children, newCh = newVnode.children
// 如果新旧虚拟节点是同一个对象,则终止
if (oldVnode === newVnode) return
// 如果新旧虚拟节点是文本节点,且文本不一样
if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) {
// 则直接将真实DOM中文本更新为新虚拟节点的文本
api.setTextContent(el, newVnode.text)
} else {
// 否则
if (oldCh && newCh && oldCh !== newCh) {
// 新旧虚拟节点都有子节点,且子节点不一样
// 对比子节点,并更新
updateChildren(el, oldCh, newCh)
} else if (newCh) {
// 新虚拟节点有子节点,旧虚拟节点没有
// 创建新虚拟节点的子节点,并更新到真实DOM上去
createEle(newVnode)
} else if (oldCh) {
// 旧虚拟节点有子节点,新虚拟节点没有
//直接删除真实DOM里对应的子节点
api.removeChild(el)
}
}
}
updateChildren方法(重要)
新旧虚拟节点的子节点对比,就是发生在updateChildren方法中
首尾指针法,新的子节点集合和旧的子节点集合,各有首尾两个指针,两两组合,进行比较,有四种情况。
总共有五种比较情况:
1、oldS 和 newS 使用sameVnode方法进行比较,sameVnode(oldS, newS)
2、oldS 和 newE 使用sameVnode方法进行比较,sameVnode(oldS, newE)
3、oldE 和 newS 使用sameVnode方法进行比较,sameVnode(oldE, newS)
4、oldE 和 newE 使用sameVnode方法进行比较,sameVnode(oldE, newE)
5、如果以上逻辑都匹配不到,再把所有旧子节点的 key 做一个映射到旧节点下标的 key -> index 表,然后用新 vnode 的 key 去找出在旧节点中可以复用的位置。
最终的渲染结果都要以newVDOM为准
**用index做key为什么不好? **
在进行子节点的diff算法过程中,会进行旧首节点和新首节点的sameNode对比。
如果在一个列表前面加一个标签,导致相同key的节点会去进行patchVnode更新文本,而原本就有的最后一个节点,被当做了新节点。
前面的都进行patchVnode更新文本,最后一个进行了新增。这样效率降低。
vue核心(MVVM)
只关注试图层
数据驱动,组件化
数据双向绑定核心:MVVM
『View』:视图层(UI 用户界面)
『ViewModel』:业务逻辑层(一切 js 可视为业务逻辑)
『Model』:数据层(存储数据及对数据的处理如增删改查)
Model 和 ViewModel 之间的交互是双向的,因此 View 的变化会自动同步到 Model,而 Model 的变化也会立即反映到 View 上显示。
当用户操作 View,ViewModel 感知到变化,然后通知 Model 发生相应改变;
反之当 Model 发生改变,ViewModel 也能感知到变化,使 View 作出相应更新。
简述 MVVM
MVVM 是 Model-View-ViewModel 的缩写。
MVVM 是一种设计思想。
Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑
View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来,
ViewModel 是一个同步 View 和 Model 的对象。
在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来。
View 和 Model 之间的同步工作完全是自动的,无需人为干涉。
因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
简述 Vue.js 的优点
1低耦合。
视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 "View" 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
2可重用性。
你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。
3独立开发。
开发人员可以专注于业务逻辑和数据的开发(ViewModel,设计人员可以专注于页面设计。
4方便测试。
界面素来是比较难于测试的,开发中大部分 Bug 来至于逻辑处理,由于 ViewModel 分离了许多逻辑,可以对 ViewModel 构造单元测试。
易用 灵活 高效。
Vue 并没有完全遵循 MVVM 的思想
严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM
vue配置反向代理解决跨域
配置:config/index.js中的proxyTable
dev{
proxyTable: {
'/api': {
target: 'http://192.168.0.1:200', // 要代理的域名
changeOrigin: true,//允许跨域
pathRewrite: {
'^/api': '' // 这个是定义要访问的路径,名字随便写
}
}
}
// /api/getMenu相当于*http://192.168.0.1:200/getMenu
// /api相当于http://192.168.0.1:200
this.$http.get("/api/getMenu", {
}
.then(res => {
})
.catch(function(error) {
});
注意: 以上面代码设置的为例,会把请求中所有带有/api字段的都替换掉,例如api/getMenu/api,前后两个都会被替换,导致404等错误,在代理数量比较多的时候容易出现这个问题。
以上配置只是在开发环境(dev)中解决跨域。
要解决生产环境的跨域问题,则在config/dev.env.js和config/prod.env.js里也就是开发/生产环境下分别配置一下请求的地址API_HOST。
开发环境中我们用上面配置的代理地址api,生产环境下用正常的接口地址。配置代码如下:
module.exports = merge(prodEnv, {
NODE_ENV: '"development"', //开发环境
API_HOST:"/api/"
})
module.exports = {
NODE_ENV: '"production"', //生产环境
API_HOST:'"http://40.00.100.100:3002/"'
}
原理
浏览器是禁止跨域的,但是服务端不禁止,在本地运行npm run dev等命令时实际上是用node运行了一个服务器,IP与后端不一致,所以会产生跨域问题,需要使用如JSONP、跨域代理等手段进行跨域请求。
而vue已经帮我们配置好了,只需要设置一下proxyTable就行。
因此proxyTable实际上是将请求发给自己的服务器,再由服务器转发给后台服务器,做了一层代理,so出现跨域问题。
底层
vue-cli采用http-proxy-middleware插件来进行代理服务器等各项配置。
Vue 组件通讯有哪几种方式
props 和 $emit父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的**
$parent,$children 获取当前组件的父组件和当前组件的子组件
$attrs 和$listeners A->B->C。Vue 2.4 开始提供了$attrs 和$listeners 来解决这个问题
先来看$attrs和$listeners的定义在Vue文档中:
vm.$attrs包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
效果
可以看到父组件App.vue中通过v-bind给Child1传递了三个值,在子组件中未被Props接收的值都在listeners将父作用域中的v-on事件监听器,传入child2
inheritAttrs 属性以及作用
当一个组件设置了inheritAttrs: false后(默认为true),那么该组件的非props属性(即未被props接收的属性)将不会在组件根节点上生成html属性,以为为对比图
父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量。(官方不推荐在实际业务中使用,但是写组件库时很常用)
$refs 获取组件实例
envetBus 兄弟组件数据传递 这种情况下可以使用事件总线的方式
vuex 状态管理
异步请求在哪一步发起?
可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
如果异步请求不需要依赖 Dom 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
能更快获取到服务端数据,减少页面 loading 时间;
ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
vue 内置指令
v-cloak可以解决插值闪烁问题(防止代码被人看见),在元素里加入 v-cloak即可
<p v-cloak>{{msg}}</p>
[v-cloak]{
display: none;
}
怎样理解 Vue 的单向数据流
数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
如果实在要改变父组件的 prop 值 可以再 data 里面定义一个变量,并用 prop 的值初始化它 之后用$emit 通知父组件去修改
Vue 如何检测数组变化
push,shift,pop,unshift,splice,sort,reverse方法进行重写
所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新
重写了数组中的那些原生方法,首先获取到这个数组的Observer对象,如果有新的值,就调用observeArray继续对新的值观察变化(也就是通过target__proto__ == arrayMethods来改变了数组实例的型),然后手动调用notify,通知渲染watcher,执行update。
vue3
1支持多个根结点,改成用fragment,
2 Composition API
setup在组件内作为 Composition API的入口,
执行时机是在 beforeCreate 之前执行
使用setup时,它接受两个参数:
props: 组件传入的属性
context:提供了this中最常用的三个属性:attrs、slot 和emit
setup 中接受的props是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。用toRefs()解决
ref就能处理 js 基本类型, 比如ref也是可以定义对象的双向绑定.取值得写.value
但是reactive函数确实可以代理一个对象, 但是不能代理基本类型,例如字符串、数字、boolean 等。
toRefs 用于将一个 reactive 对象转化为属性全部为 ref 对象的普通对象。
修改:setup在beforeCreate之前,所有生命周期名字都加上on,beforeDestory
改成onBeforeUnmount,destory改成onUnmounted
watch 与 watchEffect 的用法
watch(source, callback, [options])
参数说明:
source: 可以支持 string,Object,Function,Array; 用于指定要侦听的响应式变量
callback: 执行的回调函数
options:支持 deep、immediate 和 flush 选项。
需要侦听多个数据源时, 可以进行合并, 同时侦听多个数据
watch([() => state.age, year], ([curAge, newVal], [preAge, oldVal]) => {
console.log("新值:", curAge, "老值:", preAge); console.log("新值:", newVal,
"老值:", oldVal); });
侦听复杂的嵌套对象
const state = reactive({
room: {
id: 100,
attrs: {
size: "140平方米",
type: "三室两厅",
},
},
});
watch(
() => state.room,
(newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
},
{ deep: true }
);
默认情况下,watch 是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢?
给第三个参数中设置immediate: true即可。
stop 停止监听
const stopWatchRoom = watch(() => state.room, (newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
}, {deep:true});
setTimeout(()=>{
// 停止监听
stopWatchRoom()
}, 3000)
Object.defineProperty 与 Proxy
Object.defineProperty只能劫持对象的属性, 而 Proxy 是直接代理对象
由于Object.defineProperty只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是 Proxy 直接代理对象, 不需要遍历操作
Object.defineProperty对新增属性需要手动进行Observe
因为Object.defineProperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象, 对其新增属性再次使用Object.defineProperty进行劫持。也就是 Vue2.x 中给数组和对象新增属性时,需要使用$set才能保证新增的属性也是响应式的, $set内部也是通过调用Object.defineProperty去处理的。
Teleport 是什么呢?样式在外层,可以受内部控制
即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM
<body>
<div id="app"></div>
<div id="dialog"></div>
</body>
<template>
<teleport to="#dialog">
<div class="dialog">
122
</div>
</teleport>
</template>
<div class="header">
<navbar />
<Dialog v-if="dialogVisible"></Dialog>
</div>
Suspense 显示有数据没数据时样式
Suspense 只是一个带插槽的组件,只是它的插槽指定了default 和 fallback 两种状态。
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
slot
数据代理
1.Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的好处:
更加方便的操作data中的数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
v-html,v-text
v-html,v-text会替换掉节点中的值,{{XX}}不会
自定义指令
v-modal 可以在自定义组件上使用吗?
可以
vue模版编译原理
1模板编译,将模板代码转化为 AST;
2优化 AST,方便后续虚拟 DOM 更新;
3生成代码,将 AST 转化为可执行的代码;
const baseCompile = (template, options) => {
// 解析 html,转化为 ast
const ast = parse(template.trim(), options)
// 优化 ast,标记静态节点
optimize(ast, options)
// 将 ast 转化为可执行代码
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}