Vue
Vue的生命周期有哪些,一般在哪一步发起请求及原因?
一般分为8个阶段:创建前/后,载入前/后,更新前/后,销毁前/后。
另有两个配合keep-alive组件使用:激活前/后
1. 创建前:beforeCreate:vue实例的挂载元素el和数据对象data都为undefined,还未初始化。
在当前阶段data、methodes、computed以及watch上的数据和方法都不能被访问。
2. 创建后:created:vue的数据对象data有了,el还没有。可以做一些初始数据的获取,
但当前阶段无法与dom进行交互,如果非要,可以通过this.$nextTick()异步来操作dom。
3. 载入前:beforeMount:vue实例的el和data都已经初始化了,但还是挂载之前为虚拟的dom节点。
当前阶段虚拟dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
4. 载入后:mounted:vue实例挂在完成。当前阶段真实dom挂在完毕,数据完成双向绑定,可以访问dom节点并操作。
5. 更新前:beforeUpdate:响应式数据更新时调用,发生在虚拟dom打补丁之前,
适合在更新之前访问现有的dom,比如手动移除已添加的事件监听器
6. 更新后:updated:虚拟dom重新渲染和补丁之后调用,新的dom已经更新,避免在这个钩子函数中操作数据,防止死循环
7. 销毁前:beforeDestroy:实例销毁前调用,this能获取到实例,常用于销毁定时器,解绑事件
8. 销毁后:destoryed:实例销毁后调用,所有事件监听器都会被移除,子实例也都会被销毁
9. 激活前:activated:keep-alive组件激活时调用
10. 激活后:deactivated:keep-alive组件停用后调用
第一次页面加载时会触发 创建前/后、载入前/后
keep-alive的使用
首先简述一下keep-alive的作用,kee-alive可以缓存不活动的的组件。
当组件之间进行相互切换的时候,默认会销毁,当重新切换回来时又重新初始化。
现在有需求切换回来不销毁组件,保持原来的状态,此时用keep-alive就可以实现了
tips:
Vue 3 中的 `` 支持 `include` 和 `exclude` 属性,
允许有条件地缓存组件。这两个属性可以接受字符串、正则表达式或数组,以匹配组件的名称
生命周期钩子是如何实现的?
vue的生命周期钩子就是回调函数,创建组件实例的过程中调用对应的钩子方法。 核心是一个发布订阅模式,
将钩子订阅好,在对应的阶段进行发布
vue的父组件和子组件生命周期钩子执行顺序
父组件挂载一定是等子组件都挂在完成后,才算是父组件挂载完,所以父组件的mounted在子组件的mounted之后
渲染:父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created ->子beforeMount -> 子mounted -> 父mounted
刷新:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
销毁:父beforeDestory ->子beforeDestory ->子destroyed ->父destoryed
父组件等待子组件完成后,才会执行自己对应完成的钩子。
vue组件间通信有哪几种方式?
有三种,一种是父子组件传值,一种是兄弟组件传值,一种是隔代组件传值
1. props/$emit 适用于父子传值
2. EventBus公交传值$emit/$on
3. $parent/$children访问父子
4. vuex数据管理传值
5. localStorage与sessionStorage组件传值
6. $attrs/$listeners隔代组件通信
7. provide/inject隔代组件通信
Vuex
Vuex是一个专门为vue开发的状态管理模式
1. state:储存状态(变量)
2. getters: 对数据获取之前的再次编译,可以理解为state的计算属性
3. mutations:修改状态的方法
4. actions:异步操作,然后调用mutations来修改状态
5. modules:store的子模块,让每一个模块拥有自己的state、mutation、action、getters,
使得结构非常清晰,方便管理。
vue-router中的query与params传参与接参
query:
传参:this.$router.push({path:'/xxx',query:{id:id}})
接参:this.$route.query.id
params:
传参:this.$router.push({name:'xxx',params:{id:id}})
接参:this.$route.params.id
params传参,push里面只能是name:‘xxx’,不能是path。因为params只能通过name来引入路由
query相当于get请求,页面跳转的时候,地址栏可以看见。
params相当于post请求,参数不会在地址栏中显示 传参是$router,接参是$route
vue-router有几种钩子函数,执行流程是怎样的?
钩子函数种类有 全局守卫,路由守卫,组件守卫
1. 导航被触发
2. 离开的组件调用beforeRouteLeave守卫
3. 调用全局的beforeEach守卫
4. 复用组件里调用beforeRouterUpadate守卫
5. 路由配置里的beforeEnter守卫
6. 解析异步路由组件
7. 在被激活的组件里调用beforeRouteEnter守卫
8. 调用全局beforeResolve守卫
9. 导航被确认
10. 调用全局afterEach钩子
11. dom更新
12. 用创建好的实例调用beforeRouteEnter守卫传next的回调函数
vue-router两种模式的区别?
vue-router一共有两种路由模式。hash、history
1.hash模式:hash + hashChange
hash在url中,但不被包括在http请求中,用来指导浏览器动作,对服务端安全无用。
hash不会重加载页面,通过监听hash的变化来执行js代码,从而实现页面的改变
hash —— 即地址栏 URL 中的 # 符号
2.history模式:historyApi+popState
只要刷新这个url就会请求服务器,每一次路由改变都会重新加载
一般场景下,hash 和 history 都可以,除非你更在意颜值,# 符号夹杂在 URL 里看起来确实有些不太美丽。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成,
URL 跳转而无须重新加载页面。—— Vue-router 官网。
spa单页面的理解,优缺点是什么?
spa(single-page application)仅在web页面初始化时加载相应的HTML、JavaScript和CSS,
一旦页面加载完成,spa不会因为用户的操作而进行页面的重新加载或跳转;而是利用路由机制
实现HTML内容的变换,可以避免页面的重新加载。
优点:
1. 用户体验好、快。内容改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
2. spa相对于服务器压力小
3. 前后端分离,架构清晰,前端负责交互逻辑,后端负责数据处理
缺点:
1. 首屏(初次)加载慢:为实现spa 页面,需要将加载页面的时候将js,css统一加载,部分页面按需加载;
2. 不利于SEO:由于所有的内容都在一个页面中动态替换展示,所以SEO上有天然的弱势
new Vue()发生了什么?
new Vue()是创建了Vue实例,内部执行了根实例的初始化过程。
Vue.use是干什么的?
vue.use是用来使用插件的,我们可以在插件中扩展全局组件、指令、原型方法等。
请说一下响应式数据的理解?
根据数据类型来做不同处理,数组和对象类型当值变化时劫持
1. 对象内部通过defineReactive方法,使用Object.defineProperty()
监听数据属性的get来进行数据依赖收集,再通过set来完成数据更新的派发;
2. 数组则通过重写数组方法来实现的。扩展它的7个变更方法,通过监听这些方法做到依赖收集和派发更新;
(push/pop/shift/unshift/splice/reverse/sort)
vue3中使用proxy来实现响应式数据
Vue如何检测数组变化?
数组考虑性能原因没有用defineProperty对数组的每一项进行拦截,而是选择重写数组方法以进行重写。
当数组调用这7个方法(push/pop/shift/unshift/splice/reverse/sort)时,执行ob.dep.notify()
进行派发通知Watcher更新
Vue.$set方法是如何实现的?
给对象和数组本身都增加了dep属性,当给对象新增不存在的属性则触发对象依赖的watcher去更新,
当修改数组索引时我们调用数组本身的splice方法去更新数组。
Vue的模板编译原理?
1. template模板转换成ast语法树, .vue文件中的template是通过vue-loader来进行处理的,并不是通过运行时的编译
2. 对静态语法做静态标记
3. 重新生成代码
模板引擎的实现原理就是new Function +with来实现的。
vue-loader中处理template属性主要靠的是vue-template-compiler
$nextTick() 和 $forceUpdate()与$set
this.$nextTick():
数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,
这个操作都应该放进this.$nextTick()的回调函数中。函数执行在DOM渲染完之后
this.$forceUpdate():
迫使Vue实例重新(rander)渲染虚拟DOM,注意并不是重新加载组件。
结合vue的生命周期,调用$forceUpdate后只会触发beforeUpdate和updated这两个钩子函数,
不会触发其他的钩子函数。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件
this.$set:
如果在vue实例创建之后添加新的属性到实例(data)上,它不会触发视图更新.
用this.$set添加的属性会触发视图更新
v-if与v-show的区别
v-if是真正的条件渲染,直到条件第一次变为真时,才会开始渲染
v-show不管初始条件是什么都会进行渲染,只是用于display来进行显示隐藏
watch、computed、和methods的区别:
computed:计算属性,依赖其他属性值来进行计算,并且computed的值有缓存,
只有它依赖的属性值发生变化,computed才会重新计算;
watch:监听,监听数据的变化,每当监听的数据变化时都会执行回调 进行后续操作;
methods: 是一个方法,methods没有缓存机制,每当重新触发渲染,调用方法会将函数再一次执行;
watch没有缓存,监听的数据发生变化,会直接触发相应操作,但是支持异步;
而computed有缓存,依赖数据发生变化时才会重新计算。
所以只有在执行异步或开销较大的操作时,才使用watch,否则用computed。
当一个属性受多个属性影响的时候就需要用到computed。
当一条数据影响多条数据的时候就需要用watch。
v-model的原理
v-model本质是一个语法糖,可以看成是value+input方法的语法糖。
可以通过model属性的prop和event属性来进行自定义。
vue的底层是采用数据劫持 结合 发布-订阅模式的方法,
通过object.defineProperty()来劫持属性的setter,getter。
数据变动时发布消息给订阅者,触发响应的监听回调
watch的属性详解
handle:watch中需要具体执行的方法
deep:深度监听,当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,
此时就需要deep属性对对象进行深度监听。 在选项参数中指定 deep: true
immediate:watch时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,
只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。
在选项参数中指定 immediate: true
//举例:
watch: {
cityName: {
handler(newName, oldName) {
// ...
},
deep: true,
immediate: true
}
},
vue的插槽
插槽就是子组件中提供给父组件使用的一个占位符,用<slot>slot>表示,
父组件可以在这个占位符中填充任何模板代码,如HTML、组件等,填充的内容会替换子组件的<slot>slot>标签
1. 默认插槽:如果父组件没有传值到子组件,那子组件就用自己默认的内容显示在插槽上。
2. 具名插槽:子组件中插槽的位置用名称命名好后,父组件要用v-slot:名称来显示在子组件中相对应的插槽
3. 作用域插槽:作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,
该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。
vue常用命令
v-if / v-else / v-else-if / v-model / v-bind / v-on / v-show / v-html / v-text
vue的性能优化
1. 尽量减少data中的数据
2. 防抖节流
3. key保证唯一
4. 更多的情况下使用v-if代替v-show
5. spa页面采用keep-alive 缓存组件
6. 第三方模块按需导入
7. 图片懒加载
8. 使用cdn加载第三方模块
9. 压缩代码
vue-router与location.href的用法区别
1.vue-router使用pushState进行路由更新,静态跳转,页面不会重新加载;location.href会触发浏览器,页面重新加载一次
2.vue-router使用diff算法,实现按需加载,减少dom操作
3.vue-router是路由跳转或同一个页面跳转;location.href是不同页面间跳转;
4.vue-router是异步加载this.$nextTick(()=>{获取url});location.href是同步加载
vue的mixin
mixin可以定义公用的变量或方法,但是mixin中的数据是不共享的,
也就是每个组件中的mixin实例都是不一样的,都是单独存在的个体,不存在相互影响的;
mixin混入对象值为函数的同名函数选项将会进行递归合并为数组,两个函数都会执行,
只不过先执行mixin中的同名函数;
mixin混入对象值为对象的同名对象将会进行替换,都优先执行组件内的同名对象,
也就是组件内的同名对象将mixin混入对象的同名对象进行覆盖;
vue阻止事件冒泡与js阻止事件冒泡
vue:
.stop:阻止事件冒泡
.prevent:阻止默认事件
js:
.stopPropagation():阻止事件冒泡
.preventDefault():阻止默认事件
vue中的虚拟DOM
什么是虚拟DOM:用一个JS对象来描述一个DOM节点。
为什么要有虚拟DOM:
因为vue是数据驱动视图的,数据发生变化视图就要随之更新,
而操作真实DOM是非常消耗性能的,因为浏览器把DOM设计的非常复杂
虚拟DOM的用途:
要尽可能的少操作DOM,要通过对比数据的变化的前后状态,计算出视图中哪些地方需要更新,
用JS的计算性能来操作DOM的性能,用JS模拟出一个DOM节点,称之为虚拟DOM节点。当数据发生
变化的时候,我们对比变化前后的虚拟DOM节点,通过DOM-Diff算法计算出需要更新的地方,然后去更新需要更新的视图
VNode类:
Vue中就存在了一个VNode类,通过这个类,我们就可以实例化出不同类型的虚拟DOM节点
VNode的作用:
在视图渲染之前,把写好的template模板先编译成VNode并缓存下来,等到数据发生变化页面需要重新渲染的
时候,再把数据发生变化后生成的VNode与前一次缓存下来的VNode进行对比,找出差异,然后有差异的VNode
对应真实的DOM节点就是需要重新渲染的节点,根据有差异的VNode创建出真实的DOM节点再插入视图中,
完成一次视图更新
vue3有什么变化
1. 改使用proxy实现响应式数据
2. api进行变化,vue2组件库与vue3组件库不互通
3. 创建vue实例用 import {createApp} from 'vue' ,const app = createApp()
4. 删除掉了过滤器,建议使用方法替换或者计算属性
5. 体积更小,通过 Tree-shaking 功能,Vue 3 能够更有效地减少打包体积,只包含必要的模块
6. 引入了 Composition API,可以与 Options API 一起使用,提供了更灵活的逻辑组合与复用
7. 重写了虚拟 DOM 的实现,优化了模板编译,使得组件初始化和更新性能提高了 1.3 到 2 倍,SSR 速度提高了 2 到 3 倍
8. Vue 3 支持多根节点组件,使得组件结构更加灵活
9. props 跟 $emit 优化: Vue 3 中子组件接收的值在 `props` 对象中,
需要通过 `props.name` 访问,而 emit 触发的事件需要在 `defineEmits` 中声明
10. Vue 3 引入了 `Teleport` 组件,允许将组件内容渲染到 DOM 中的任何位置
vue3中的ref 与 reactive
在 Vue 3 中,你可以使用 `ref` 来声明简单数据类型,也可以使用 `reactive` 来声明复杂数据类型。
实际上,`ref` 和 `reactive` 都可以用于任何类型的数据,但它们的使用场景和性能特点有所不同
import { ref, reactive } from 'vue';
// 使用 ref 声明简单数据类型
const count = ref(0);
// 使用 ref 声明复杂数据类型
const user = ref({
name: 'Kimi',
age: 30
});
// 使用 reactive 声明简单数据类型
const countReactive = reactive({
count: 0
});
// 使用 reactive 声明复杂数据类型
const userReactive = reactive({
name: 'Kimi',
age: 30,
friends: ['Alice', 'Bob']
});
在模板中使用这些响应式数据时,`ref` 需要使用 `.value` 来访问值:
<div>{{ count.value }}div>
<div>{{ user.value.name }}div>
而 `reactive` 则不需要:
<div>{{ countReactive.count }}div>
<div>{{ userReactive.name }}div>
在 Vue 3 中,`reactive` 对象中的 `ref` 属性会被自动解构
const user = reactive({ name : ref('aa')});
console.log(user.name); //为'aa',不需要user.name.value
Vue3.x响应式数据原理
1. Proxy 判断当前Reflect.get的返回值是否为Object,
如果是则在通过reactive方法做代理,这样就实现了深度观测。
2. 检测数组的时候可能触发多次get/set,怎么防止触发多次?
可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,
只有满足两个条件之一,才有可能执行trigger
Proxy 与 Object.defineProperty 优劣对比
1. Proxy可以直接监听对象而非属性
2. Proxy可以直接监听数组变化
3. Proxy有多达13种拦截方法,不限于apply、ovnKeys、deleteProperty、has等等是Object.defineProperty不具备的
4. Proxy返回的是是一个新对象,而Object.defineProprery只能遍历对象属性直接修改
5. Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
1. Object.defineProprety兼容性好,支持IE9,而Proxy存在浏览器兼容问题。
script setup 和通过 defineComponent 引入 setup
在 Vue 3 中,`<script setup>` 和通过 `defineComponent` 引入 `setup` 函数
是两种不同的组件定义方式,它们有以下区别:
1.script setup
是Vue 3.2+ 版本中引入的编译时语法糖,它提供了一种更简洁的方式来使用 Composition API。
使用 `