一. $nextTick使用以及原理
A: 使用: 它可以在DOM更新完毕之后执行一个回调; 原理: 先来了解下,浏览器的机制宏任务微任务
宏任务(Macrotask): setTimeout,setInterval ,script(整体代码块) ,MessageChannel I/O,事件队列 微任务(Microtask) :MutationObserver(浏览器环境),Promise.[ then/catch/finally ], process.nextTick(Node环境)
1.1、$nextTick有什么用?
Vue是异步渲染的框架。 data改变之后,DOM不会立刻渲染。
n
e
x
t
T
i
c
k
会在
D
O
M
渲染之后被触发,以获取最新的
D
O
M
节点。连续多次的异步渲染,
nextTick会在DOM渲染之后被触发,以获取最新的DOM节点。 连续多次的异步渲染,
nextTick会在DOM渲染之后被触发,以获取最新的DOM节点。连续多次的异步渲染,nextTick只会执行最后一次渲染后的结果。
1.2、$nextTick的原理
$nextTick: 异步执行传入的回调函数(是通过事件循环中的任务队列的方式)。
- data数据更新 => 生成新的虚拟dom => diff算法比较新旧虚拟dom => patch更新变化的虚拟dom到真实的dom上 => 触发更新回调。
- diff算法比较新旧虚拟dom, 比较后,在patch更新虚拟到真实dom之前,触发this.$nextTick。
(vue中更新DOM的方法也是通过nextTick进行调用的)。 - 触发this.$nextTick后,虚拟dom更新到真实dom上之前,开始处理里面的回调。
这个时候会判断下当前环境(浏览器)支持哪种任务:Promise,MutationObserver,setImmediate,setTimeout。 - 如果支持promise(走promise): patch更新变化的虚拟dom到真实的dom上,然后.then开始处理里面的回调函数;
如果走MutationObserver,patch更新变化的虚拟dom到真实的dom上,然后开始处理里面的回调函数; 如果走setTimeout, patch更新变化的虚拟dom到真实的dom上,然后开始处理里面的回调函数
代码执行步骤:
const fun = () =>{
console.log("DOM更新完成了")
}
this.$nextTick(fun)
new Promise((reslove) => {
}).then(() => {
fun()
});
new MutationObserver(() => {
fun()
});
setTimeout(() => {
fun()
})
1.3、循环调用的话nextTick里面有容错机制吗?
多次调用 nextTick 会将方法存入队列 callbacks 中,通过这个异步方法清空当前队列。
二. VUE3中watch与watchEffect
2.1、 watchEffect 立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
watchEffect的一些特点:
不需要手动传入依赖(不用指定监听对象) 无法获取原始值,只能获取更新后的值 立即执行(在onMounted前调用) 一些异步操作放里面更加的合适 watchEffect第一个参数是一个箭头函数(是一个副作用函数),里面用来获取侦听到的新值以及用来进行其它操作(比如清除副作用等)
import { watchEffect } from 'vue'
const count = ref(0)
watchEffect(() =>{
console.log(count.value)
})
setTimeout(() => {
count.value++
}, 1000)
进入页面,直接打印结果:1
watch监听的一些特点: 监听单一数据,监听多个数据,监听基本数据,监听对象属性;
watch监听基本数据类型:
<template>
<div>
<h1 @click = "changeTitle">{{title}}</h1>
</div>
</template>
<script>
export default {
data(){
return {
title:'标题',
}
},
methods:{
changeTitle(){
this.title += 'kk'
},
},
watch:{
title(oldval, newval){
console.log('oldval', oldval)
console.log('newval', newval)
},
}
}
</script>
监听对象中的属性:
<template>
<h1 @click = "changeTitleObj">{{obj.a}}</h1>
</template>
<script>
export default {
data(){
return {
obj:{
a:'对象中的属性'
}
}
},
methods:{
changeTitleObj(){
this.obj.a += '22'
}
},
watch:{
obj(newval){
console.log('对象newval', newval)
},
obj:{
handler(oldval, newval){
console.log('对象深度oldval', oldval)
console.log('对象深度newval', newval)
},
deep: true,
},
'obj.a' (val){
console.log('val', val)
}
}
}
</script>
VUE3中watch与watchEffect —— 全网最详细系列
三. Ref reactive区别,底层怎么实现响应式的
reactive与ref对比 从定义数据角度对比:
- ref用来定义:任意数据类型
- reactive用来定义:对象(或数组)类型数据
如何选择 ref 和 reactive?
- 基础类型值(String,Number,Boolean,Symbol) 或单值对象(类似{ count: 1 }这样只有一个属性值的对象) 使用 ref
- 引用类型值(Object、Array)使用 reactive
从原理角度对比:
ref通过Object.defineProperty()的get和set来实现响应式(数据劫持)。
reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
从使用角度对比:
- ref定义的数据:访问或更改数据需要.value
- reactive定义的数据:操作数据与读取数据均不需要.value。
toRef 针对一个响应式对象(reactive封装)的prop(属性)创建一个ref,且保持响应式
两者保持引用关系
语法:const 属性名= toRef(对象,‘属性名’)
toRefs toRefs 是一种用于破坏响应式对象并将其所有属性转换为 ref 的实用方法
将响应式对象(reactive封装)转成普通对象
对象的每个属性(Prop)都是对应的ref
两者保持引用关系
语法:const 属性名= toRefs(对象,‘属性名’)
四. vue监听数组
Vue2为什么不能监测数组的变化 首先从表象上来看,Vue2对数组的响应式实现是有些不足的:
- 无法监测数组的新增
- 无法监测用索引改变数组的操作
先来简单分析下为什么会存在上述问题: 我们知道Vue2是通过Object.defineProperty方法来进行数据监测的。 从上述结果可以看出,Object.defineProperty是可以对数组进行监测的,但是Vue2为什么没用呢,其实是出于性能的考虑,数据一般会被频繁的改动,每次的改动都需要遍历整个数组,给数组属性重新observe,这样会极大的消耗性能,因此在Vue2中hack了Array上的一些方法。
1、首先还是定义一组数据用于展示,hobbys 为字符串数组,friends 为对象数组
const vm = new Vue({
el: '#root',
data() {
return {
hobbys: ['抽烟', '喝酒', '烫头'],
firends: [
{ name: 'al', age: 20 },
{ name: 'hj', age: 22 }
]
}
},
})
2、展示完了之后,我们在控制台上查看 vm.data 发现 两个数组中的值,并没有像对象中的值一样,绑定一个 get 和 set 方法。
这也就是意味着,如果我通过 vm._data.hobbys 去修改 hobbys 的第一个值的时候,Vue内的数据是会修改的,但是,因为没有 set 函数,无法监听到数据的改变,所以页面上是不会重新渲染修改后的属性值的。例如下图 Vue-数组变更方法 Vue 收集了我们平时操作数组的方法,发现 push、pop、shift、unshift,splice,sort、reserve这七个操作数组的方法,都会对原数组产生影响,反之像 map、filter、some、every等数组遍历的方法都只是会产生一个新的数组,并不会对原数组产生影响。
所以 Vue 将这7个方法进行了处理,并规定如果调用了这七个操作数组的方法,那我的 Vue 才会去监听数据的改变,如果是map等方法,那你原数据都没有改变,我也不用监听了。所以,当我想去操作数组中的属性的时候,我们不能直接 通过序号来操作,而是需要调用数组的这七个方法。 那么 Vue 包装之后的这些方法里面又是怎么做的来让Vue实现对数据监听的呢?我们还是以push为例,当调用 vm中数组原型上的 push 方法之后,分别作了如下操作
1、调用原生的 push方法,改变数组数据
2、生成新的虚拟DOM,新旧虚拟DOM对比,模板编译、页面重新渲染
总结: 1、Vue 监听数组改变,使用的是 数组的变更方法,包括:push、pop、shift、unshift、splice、sort、reserve,因为这七个方法操作数组之后,会改变原数组
2、这七个方法不是Array.prototype 上的方法,而是经过 Vue 的包装处理之后,挂载到Vue 的 Array.prototype 上的
3、同时也可以使用 set 方法,以此来改变当前数组,且Vue还能监听到,不过基本不这么使用
VUE3检测数组: Vue3是用Proxy来进行对象、数组的代理,其实只要理解了Proxy就明白Vue3为什么会抛弃Object.defineProperty了:
Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。 由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而 Proxy 直接代理对象,不需要遍历操作。 Object.defineProperty对新增属性需要手动进行Observe。 由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。 Proxy 具有更多的拦截支持,可以做的更精细化的控制
五. vue.
s
e
t
,
v
u
e
.
set, vue.
set,vue.delete
this.$set的使用
就是当data中包含声明且已赋值的对象或者数组(数组包对象)时,我们要向当前的这个对象中添加一个新的属性并且更新,结果发现并不会更新视图,
受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。
所以在data中注册的对象是响应式的,而后来添加的属性不是响应式的,只是个普通的属性,为了避免上面出现的问题,我们使用this.$set来向响应式对象中添加响应式的新属性
语法:this.$set(target,key,value)
<script>
export default {
data() {
return {
student: {
name: '张三',
}
}
},
methods: {
setMessage() {
this.$set(this.student, 'age', 15)
console.log(this.student)
}
}
}
</script>
delete与$delete
var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[1]
console.log(a)
this.$delete(b,1)
console.log(b)
let obj={a:1,b:2}
六. Vue页面优化
在vue中,可以通过路由配个路由占位符来完成单页面应用的实现,其原理时通过对路由占位符的更新来完成单页面应用的实现。 单页面应用的优点在于页面的切换不会导致整个页面的刷新,而是对路由占位符的更新,比起传统的,单页面应用切换页面速度更快、用户体验更好,代码的样式及标准更好控制,程序员的工作量更少。缺点在于单页面的首屏加载速度较慢,SEO不友好。 1. 缩小项目体积: 原理:体积越小,加载越快。 方法:
通过webpack对项目体积进行压缩,开启gzip压缩文件 通过对css3、js文件的合并,如在两不同组件中,拥有相同的样式,可通过全局css文件中设置。在js文件上,将相同的方法封装合并成一个方法,如API请求。 减小图片体积,图标可通过矢量图来代替。
2. 减少加载模块:
原理:单页面应用的首屏加载较慢主要是因为单页面应用在首屏时,无论是否需要都会加载所有的模块,可通过按需加载、路由懒加载来优化。 方法:
按需加载,通过对路由文件的配置,来对相关模块划分区间,如登录界面可以和首页、主页面划分一块,在进入首屏时,只对首屏所在的区块进行加载。通过require.ensure()来将多个相同类的组件打包成一个文件。如示例代码,打包时,将两个组件打包成一个js文件,文件名为good。
{
path: '/goodList',
name: 'goodList',
component: r => require.ensure([], () => r(require('../components/goodList')), 'good')
},
{
path: '/goodOrder',
name: 'goodOrder',
component: r => require.ensure([], () => r(require('../components/goodOrder')), 'good')
}
动态加载,通过import来实现路由的动态加载,这个时候对于路由的加载是动态的,用到再加载。
{
path: '/goodList',
name: 'goodList',
component: () => import('../components/goodList')
},
{
path: '/goodOrder',
name: 'goodOrder',
component: () => import('../components/goodOrder'),
}
3. 代码层面的优化 1 v-for 遍历为 item 添加 key 在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。
2 v-for 遍历避免同时使用 v-if v-for 比 v-if 优先级高,这意味着 v-if 将分别重复运行于每个 v-for 循环中,将会影响速度。建议替换成 computed 属性。
3 v-if 和 v-show 区分使用场景 v-if 是真正的条件渲染,也是惰性的:如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块。 v-show 就简单得多,不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行样式切换。 v-if 适用于很少改变条件,不需要频繁切换的场景;v-show 则适用于需要非常频繁切换的场景。
4 computed 和 watch 区分使用场景 computed 计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值; watch 更多的是观察的作用,每当监听的数据变化时都会执行相关回调。
当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算; 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,watch 允许我们执行异步操作。 5 keep-alive 利用keep-alive包裹,将不活动的组件缓存起来。 在组件切换过程中将状态保留在内存中,防止重复渲染dom,减少加载时间及性能消耗,提高用户体验。
6 图片资源懒加载(使用插件) 对于图片过多的页面,为了加快页面加载速度,可以将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。我们在项目中使用 Vue 的 vue-lazyload 插件。 四.Webpack 层面的优化
1 Webpack 对图片进行压缩 对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用 image-webpack-loader 来压缩图片
2 模板预编译 打包时,直接把组件中的模板转换为render函数,这叫做模板预编译。这样一来,运行时就不再需要编译模板了,提高了运行效率。
五. 基础的 Web 技术优化化
1 浏览器缓存 为了提高用户加载页面的速度,对静态资源进行缓存是非常必要的。
七. Vue2 vue3怎么父子传参
八. 自定义指令
九. Mixin的使用以及哪些不足?
mixin: 把多个组件共同的配置,抽取出来成一个混入对象; 看代码: mixinx/mixin.js
let mixin = {
created() {
console.log("我是mixin里面的created!")
},
methods: {
hello() {
console.log("hello from mixin!")
}
}
}
export default mixin
home.vue文件中使用:
<template>
<div class="home">
<span>This is a Home page</span>
</div>
</template>
<script>
import myMixins from "../mixins/mixin.js";
export default {
name: 'Home',
mixins: [myMixins]
}
</script>
真实项目中使用:是因为这个接口好多个页面使用,所以做了mixins处理: dictData.js:
**在这里插入代码片let dictData = {
data() {
return {
dictStatusList:[],
}
},
methods: {
getDictData(dictType) {
this.$API.system.dict.data.list.post({
dictType
}).then(res => {
if(res.code == "2000") {
this[`${dictType}List`] = res.data;
}
})
},
},
};
export default dictData;
home.vue文件使用:
import dictData from "@/mixins/dictData";
export default {
mixins:[dictData],
created() {
this.getDictData("dictStatus");
this.getDictData("dictYesNo");
},
}
十. vue2 vue3的区别
十一. hash和history
十二. 虚拟DOM循环算法
十三. hash和history
十四. 路由守卫
十五. 打包:打包loader plug-in,热加载
Watch监听的原理, Template一个跟标签
路由守卫
Vue3的vuex History模式刷新后,404怎么办
-
整个路由的原理 -
枚举: 一个对象有10个属性,用枚举的方式,拿出7个; readonly , writeable -
页面优化,webpack -
事件循环机制,应用场景 -
ES6 新特性 -
ref reactive的原理 -
Vue2 vue3 区别
|