Vue2
生命周期钩子函数
- 初始化前后、dom渲染前后、数据更新dom更新前后、组件销毁先后
- created (可以开始发送ajax请求,获取data内的数据,保存数据)
- mounted (可以开始操作dom例如初始化echarts)
- beforeCreate:data数据初始化之前,组件还没有数据
- created: data数据初始化之后,可以获取到组件的数据
- beforeMount:DOM渲染之前,DOM还没渲染
- mounted:DOM渲染之后,可以操作DOM了
- beforeUpdate: 数据更新,DOM更新前
- updated: 数据更新,DOM更新后
- beforeDestroy: 组件销毁前
- destroyed: 组件销毁后
Vue 的父组件和子组件生命周期钩子函数执行顺序
1、Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
1)加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
2)子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
3)父组件更新过程
父 beforeUpdate -> 父 updated
4)销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
响应式原理
双向数据绑定采用了数据劫持来实现,通过Object.defineProperty()来劫持各个属性的 setter 和 getter,从而实现了数据的响应式更新。getter 方法用于获取数据,setter 方法用于监听数据的变化并更新数据。
缺陷是只能对data节点内定义的数据进行数据劫持,不能监听对象的新增属性和删除属性。
vue更新数组不能通过索引,可通过pop、push、shift、unshift、splice、sort、reverse来实现。
解决缺陷的方法通过 Vue.set() 或者 this.$set() 来添加新属性,也可以监听数组的变化,需要手动调用
为什么data是一个函数而不是对象
如果是一个对象的话,组件被多次使用,里面的对象的数据存储的是在一个地址里面的,如果修改数据,其他组件里面的数据也会被修改,造成数据污染;采用函数的形式就不会出现这种情况,每一次创建的时候,函数都会return一个新的对象,因为每个对象的地址不同。
Vue3
生命周期钩子函数
- onMounted:组件渲染完毕:发请求,操作dom,初始化图表
- onUnmounted:组件卸载后,清除定时器、延时器、事件监听等等
setup:函数是在组件实例被创建之前调用的,它是使用 Composition API 的入口点。
onBeforeMount: 在挂载开始之前被调用。
onMounted: 在挂载完成后被调用。
onBeforeUpdate: 在更新开始之前被调用,这里可以拿到新的 props 和 reactive 数据。
onUpdated: 在更新完成之后被调用。
onBeforeUnmount: 在卸载之前被调用。
onUnmounted: 在卸载之后被调用。
选项api和组合api名字不一样
响应式原理
vue3使用了Proxy,是对于整个对象的劫持,对象内部要是发生了变化,都会经过外层的proxy,无需递归,效率高。对于数组也是如此。
父通过ref或者通过$parent获取组件上的数据必须要defineExpose 暴露才能取到
Vue2&Vue3相关
vue2和vue3的区别
- 性能优化,更快的初始化时间、渲染性能以及更新性能
- 响应式原理优化
- 更好的TS支持
- 组合式API的支持,省去this的指向
- 多个根节点
- 生命周期函数变化,可以有多个相同的生命周期,用setup代替了v2的前两个生命周期函数
Vue的优点是什么
- 响应式数据绑定:当数据发生变化时,视图自动更新,提高了开发效率。
- 组件化开发:将一个页面拆分成多个组件,每个组件具有自己的逻辑和样式,便于代码的维护和重用。
- 轻量高效:轻量级的框架,体积小、性能高,加载速度快,能够快速构建响应式的单页面应用。
- 生态丰富:有大量的第三方库和插件,能够快速地解决各种问题,如数据管理、路由管理、UI 组件库等。
- 用户群体大:大量的文章、视频和教程,能够帮助开发者快速解决问题和提高开发效率。
v-if和v-for为什么不建议一起用
VUE2中v-for优先级高,所以不建议一起用 VUE3中v-if优先级高,不需要在意了
- 因为v-for的优先级比v-if高,一起用的情况下会优先进行v-for的遍历,然后在看v-if的布尔值,这样如果布尔值是false的情况下,会先渲染出来,再销毁,就会带来性能方面的浪费,避免这种情况的话可以在外层套个template标签,因为这个标签渲染不会生成dom节点
Object.defineProperty 和 proxy的差异
- Object.defineProperty 是对于对象属性的劫持, 需要⼀个个递归劫持, 效率低
- Object.defineProperty 对于数组的更新检测, ⽐较麻烦,需要使用数组API更新才能被检测到
- proxy是对整个对象的代理。只要访问了对象的属性,就会经过代理,触发相应的操作。
- proxy对于数组的变化检测更为简单,不需要特殊处理数组的方法。任何修改数组的操作都能被捕获到,包括数组元素的增加、删除和更改。
虚拟DOM|diff算法|vue中的key
虚拟dom是一个用JavaScript对象表示Web页面的概念,通过对比实际DOM的变化来提高页面性能和交互体验,并使代码更加易于管理和维护。
vue中的key是使用v-for遍历数据时,key作为vue中对比算法的标识,为了在进行虚拟 DOM 的比对时,方便 Vue 找到对应的节点,在数据修改后,vue会根据新数据生成新的虚拟DOM。对比下来虚拟DOM与真实DOM有差异的话会替换掉真实DOM。虚拟DOM没变的话就直接使用之前的真实DOM。若在旧的虚拟DOM中没有找到与新虚拟DOM相同的key,则会创建一个真实DOM进行渲染。
总之,key 属性的作用是为了帮助 Vue 进行虚拟 DOM 的比对,减少不必要的 DOM 操作,提高页面渲染效率。
这个key必须是唯一值,若使用数组下标则在数组中插入或者前增会出现错乱的问题。
watch和computed区别
computed能完成的watch都能完成,watch可以写异步代码,computed不行!
- watch:是一个侦听器,当数据发生变化时,会依赖于依赖于现有的数据的值并派生出新的数据的值,当要监听复杂数据类型时需要开启深度监听。复杂数据类型需要开启深度监听deep:true,还可以添加immediate:true 开启首次监听
- computed:是计算属性,有缓存,不能和data中的数据重名,比方说动态控制封装的弹窗的标题,可以通过computed来实现。
watch和computed的原理
- 当Vue组件实例化时,Vue会遍历
watch
选项中的每个属性,将它们转化为观察者(Watcher)对象,包含了两个主要函数:getter
和setter
。getter函数用于收集依赖,并添加到依赖收集器中,setter通知依赖收集器中的观察者对象,告诉它们需要更新视图,观察者对象会执行相应的回调函数来更新视图。 - 当Vue组件实例化时,Vue会遍历
computed
选项中的每个计算属性,计算属性的值会被缓存起来,只有在其依赖的响应式数据属性发生变化时,才会重新计算。Vue会在计算属性的getter
函数执行时进行依赖追踪,记录哪些响应式数据属性被计算属性所依赖。当依赖的响应式数据属性发生变化时,计算属性的缓存会被标记为过期,下次访问计算属性时会重新计算,并将新值缓存起来。计算属性的值是惰性求值的,它只会在需要的时候才计算,并且只计算一次,然后进行缓存,以提高性能。 - 总结:监听器和计算属性都依赖于Vue.js的响应式数据系统,通过依赖追踪和观察者模式来实现数据变化的监听和响应。这种机制使Vue能够高效地更新视图以及处理复杂的数据依赖关系。
混入
- vue2中的混入在一个js文件中写和vue export default一样 例如
- export default{ data(){return{}},created()
- 使用方法:先导入写的js文件, 再在.vue文件中的export default内写 mixins: [导入的名字],即可直接使用
- 说明:data数据字段,methods内的方法重名,自己的高于混入的,同字段名自己的覆盖混入的
- 生命周期函数都会执行,先执行混入的,后执行自己的
- 优点:封装复用
- 缺点:数据来源不清晰,后期维护性差
组件通信
- 父传子:冒号传参props接收 父获取子:给子组件ref
- 子传父:this.$emit('绑定在子组件上的方法名',id); //id是传的参数 ;
- 子获取父: $parent进行传递 慎用。嵌套组件时需要要多个$parent ,跟写的位置有关
- 祖先与后代: Provide 与 Injectc
- **复杂关系:**通过vuex/pinia存放共享的变量
#vue3 事件总线简单方法
npm i mitt
#在src创建个bus文件夹,内放个index.js
//引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from 'mitt';
const $bus = mitt();
export default $bus;
#在组件内导入使用
import $bus from '@/bus'
$bus.emit('cp1-send', data)#发布
$bus.on('cp1-send', (data) => {#接收
console.log(data)
})
#vue2 在src创建个bus文件夹,内放个index.js
import Vue from 'vue';
// 创建一个空的 Vue 实例作为事件总线
const EventBus = new Vue();
export default EventBus;
#在组件内导入使用
import EventBus from './EventBus';
EventBus.$emit('send-data', 'Data from Component A');#发布
#接收 监听事件,在组件创建时就开始监听
mounted() {
EventBus.$on('send-data', data => {
console.log(data)
});
}
组件缓存keep-alive
用于缓存不活动的组件实例,以避免在组件切换时反复创建和销毁组件实例,从而提高性能和用户体验。
- 用他包裹组件时,会缓存不活动的组件实例,而不是销毁,在组件的切换过程中将状态保留到内存中,防止重复渲染DOM,可以减少加载事件及性能消耗,提升用户体验。有两个生命周期函数,activated激活状态的生命周期函数 deactivated 失活状态的生命周期钩子函数。
- 原理是通过created函数将需要缓存的VNode 节点保存在this.catch中,在页面渲染时再从this.catch中取出相应的节点并渲染
路由
- 本质上是映射关系,根据不同的URL请求,返回对应不同的资源,那么url地址和真实的资源之间就有一种对应的关系就是路由
- 作用是设定访问路径,并将路径和组件映射起来(就是用于局部刷新页面,不需要请求服务器来切换页面)
- 静态路由:将不需要权限就能访问的页面称为静态路由 比如登录页 404页面
- 动态路由:两种:需要权限才能访问的页面
- params传参:需要在路由页面配置path: '/user/:a/:b',获取通过route.params.a
- query传参:router.push('/user?id=123');
$nextTick
- Vue在更新DOM时是异步执行的,当数据变化时,通过nextTick开启个异步更新队列,等数据更新完毕后,再进行更新视图
- 原理是利用了Promise、MutationObserver、setTimeout,根据环境支持情况选择最合适的异步机制来执行回调更新DOM
.native
- 某些组件绑定点击事件不成功需要添加.native修饰符,因为组件内部没有做处理,使用修饰符后就作用到了组件的根元素上
vuex是数据集中管理工具
- State:用来存数据
- Getter:类似计算属性,基于state内的数据派生出新的数据,当state内依托的数据变了才会重新计算。
- mutations: 修改共享数据的唯一节点 此方法内的函数的第一个参数是state。
- Actions:涉及到异步操作,比如发请求要放在actions中,在actions中修改数据需要调用mutations中的方法 action中方法的第一个参数是context,里面有commit方法,通过这个方法修改context.commit('写在mutations中的方法',传的参数)
- Modules:模块化路由导入的地方。每一个模块的路由必须开启namespaced:true
为什么有了localstorage还要用vuex
Vuex
主要用于在应用程序内共享和管理数据,确保数据在各个组件之间一致。- localStorage 存的只能是字符串,如果存储的内容不是字符串,还需要转换,有额外的性能消耗。
- localStorage 里面的数据变化不能进行监控
- 主要用途就是可以响应式的处理数据
vuex仓库值初始化数据比较复杂时,state可以是函数写法
vuex辅助函数
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
computed: {
...mapState('storeA', ['countA']), // 映射 storeA 中的 countA 到计算属性
...mapGetters('storeA', ['doubleCountA']), // 映射 storeA 中的 doubleCountA 到计算属性
},
methods: {
...mapMutations('storeA', ['incrementA']), // 映射 storeA 中的 incrementA 到方法
...mapActions('storeA', ['asyncIncrementA']), // 映射 storeA 中的 asyncIncrementA 到方法
},
};
pinia与vuex
pinia仓库支持Composition API 风格,更好的支持TS,不再有modules的嵌套结构,也没有命名空间的概念,不需要记住复杂的关系
jsexport const useCounterStore = defineStore('counter', () => { const count = ref(0) function increment() { count.value++ } return { count, increment } })
路由的hash跟history模式
- hash:兼容性好,url中带有#,可以监听hashchange事件来获取到新旧路由地址,e.newURL,e.oldURL
- history:没有#,美观,但需要后端支持,url改变会触发popState事件
自定义指令控制图片加载失败时显示默认图片
自定义指令生命周期:
bind
:在元素与指令绑定时执行,只执行一次。inserted
:在元素插入到 DOM 后执行。update
:在包含指令的元素更新时执行。componentUpdated
:在包含指令的元素及其子元素都更新后执行。
图片懒加载
将图片真实地址存到data-src属性上 ==》 判断图片是否在可视区域 ==》 如果在,就设置图片src
- 通过IntersectionObserver实例来检测元素是否进入视口,参数是一个函数,函数有两个参数分别是entries和接收IntersectionObserver的实例,entries是多个被观察者元素信息组合成的数组,然后对每一个被观察者信息进行遍历,如果进入视口,那么就将元素的src属性替换为data-src属性,并停止该元素的检测,避免重复加载
- 先实例化IntersectionObserver,然后再调用observer.observe(el)传入绑定的元素 先声明再使用
注:此处el.src=binding.value与el.src=el.dataset.src相等 因为使用的时候即设置了:data-src又给自定义指令传了值
插槽
- 用于动态地传递和渲染内容。主要目的是允许你在一个组件中定义可替换的内容,并在组件实例化时动态填充这些内容。