一文读懂Vue生命周期(Vue2)

news/2024/5/20 23:45:40

一文读懂Vue生命周期(Vue2)

目录

    • 一文读懂Vue生命周期(Vue2)
      • 1 前言
      • 2 Vue生命周期
        • 2.1 基本生命周期
          • 2.1.1 8个生命周期
          • 2.1.2 案例
        • 2.2 组件生命周期
          • 2.2.1 父子生命周期
          • 2.2.2 案例
        • 2.3 keep-alive生命周期
          • 2.3.1 案例
        • 2.4 其他
      • 3 总结

vue生命周期详解,vue更新生命周期,keepalive生命周期,

1 前言

对于一个Vue开发者而言,学习Vue生命周期是必不可少的内容,事实上学习Vue生命周期对于开发高质量的Vue应用至关重要。在初级阶段,需要学习基本组件的生命周期,以了解页面的加载逻辑。而在Vue工程化阶段,就必须要对Vue生命周期有更深的了解。这对以下几个方面来说是基础:

  1. 理解组件行为:通过了解Vue的生命周期,你可以更深入地理解Vue组件是如何创建、更新和销毁的。这有助于你预测组件在不同阶段的行为,并避免潜在的问题。
  2. 优化性能:在生命周期的不同阶段,你可以执行特定的操作来优化应用的性能。例如,在created阶段,你可以进行数据的初始化或发送网络请求,以避免在模板渲染时产生不必要的延迟。在beforeDestroy阶段,你可以清除定时器、解绑全局事件或销毁子组件,以防止内存泄漏。
  3. 更好的状态管理:Vue的生命周期允许你在组件的不同状态下执行特定的逻辑。例如,在updated阶段,你可以检查数据的变化并据此更新DOM或触发其他操作。这有助于你更好地管理组件的状态和响应数据的变化。

2 Vue生命周期

学习Vue生命周期是从简单到复杂的循序渐进过程,以下我们把其拆解成三个部分:

  1. 基本生命周期:单个Vue实例的生命周期;
  2. 组件生命周期:添加子组件时加载的生命周期;
  3. keep-alive生命周期:当使用keep-alive包裹组件时,该组件的生命周期;
2.1 基本生命周期
2.1.1 8个生命周期

Vue2基本生命周期钩子函数有11个,主要的生命周期有8个;主要的8个生命周期如下:

  1. beforeCreate(创建前):在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
  2. created(创建后):在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,以及 watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
  3. beforeMount(挂载前):在挂载开始之前被调用:相关的 render 函数首次被调用。此时模板编译已经完成但是还未挂载到页面上。
  4. mounted(挂载后):el 被新创建的 vm.el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个in−document元素,当mounted被调用时vm.el 也在文档内。此时可以访问到 $el 属性,也可以操作 DOM 和通过 AJAX 获取数据。
  5. beforeUpdate(更新前):数据更新时调用,发生在虚拟DOM打补丁之前。可以在这个钩子中访问现有的DOM,比如手动移除已添加的事件监听器。
  6. updated(更新后):由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以你现在可以执行依赖于DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。
  7. beforeDestroy(销毁前):实例销毁之前调用。在这一步中,实例仍然完全可用。
  8. destroyed(销毁后):Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑,所有的事件监听器都会被移除,所有的子实例也会被销毁。

官网对8个生命周期图解如下:

生命周期

2.1.2 案例

仅仅学习理论是不够的,我们来通过案例来看各个生命周期实际执行顺序和各个生命周期能访问得到的内容:

执行顺序:

直接上最简单的能触发所有生命周期代码:

<template><div><title ref="myPage">生命周期</title><div>参数:{{ parama }}</div><el-button @click="updateParama">更新数据</el-button><el-button @click="leavePage">离开页面</el-button></div>
</template>
<script>
import Vue from "vue";
import { Button } from "element-ui";
Vue.use(Button);export default {beforeCreate() {console.log("页面加载:beforeCreate");},created() {console.log("页面加载:created");},beforeMount() {console.log("页面加载:beforeMount");},mounted() {console.log("页面加载:mounted");},beforeUpdate() {console.log("页面更新:beforeUpdate");},updated() {console.log("页面更新:updated");},beforeDestroy() {console.log("页面销毁:beforeDestroy");},destroyed() {console.log("页面销毁:destroyed");},data() {return {parama: 0};},methods: {leavePage() {this.$router.push({ path: "/home" });},updateParama() {this.parama++;}}
};
</script>

页面效果如下:

在这里插入图片描述

当进入该页面时,触发前四个生命周期,执行顺序如控制台打印:

页面加载:beforeCreate
页面加载:created
页面加载:beforeMount
页面加载:mounted

当点击"更新数据"按钮时,依次触发beforeUpdate,updated;控制台打印:

页面更新:beforeUpdate
页面更新:updated

当点击“离开页面”按钮时,依次触发beforeDestroy,destroyed;控制台打印:

页面销毁:beforeDestroy
页面销毁:destroyed

大家可以复制代码自己试一下;

访问数据

再来看各个生命周期页面数据的访问情况,我们在每个生命周期都打印一下数据,代码如下:

beforeCreate() {console.log("页面加载:beforeCreate");console.log(this.parama);},created() {console.log("页面加载:created");console.log(this.parama);},beforeMount() {console.log("页面加载:beforeMount");console.log(this.parama);},mounted() {console.log("页面加载:mounted");console.log(this.parama);},beforeUpdate() {console.log("页面更新:beforeUpdate");console.log(this.parama);},updated() {console.log("页面更新:updated");console.log(this.parama);},beforeDestroy() {console.log("页面销毁:beforeDestroy");console.log(this.parama);},destroyed() {console.log("页面销毁:destroyed");console.log(this.parama);},

首先是页面加载时,控制台打印如下:

页面加载:beforeCreate
parama undefined页面加载:created
parama 0页面加载:beforeMount
parama 0页面加载:mounted
parama 0

我们可以看到beforeCreate时是访问不到数据的,因为属性还未初始化;其他生命周期都能正常访问到数据。

我们再来看一下数据更新,点击“更新数据按钮”,控制台打印如下:

页面更新:beforeUpdate
parama 1页面更新:updated
parama 1

我们可以看到两个更新的生命周期打印的数据相同,这里要注意的是,数据更新与生命周期触发的顺序是:

parama变成1 -> beforeUpdate -> updated

到这里大家都能明白生命周期顺序与数据访问之间的关系了。

2.2 组件生命周期
2.2.1 父子生命周期

当页面没有引入子组件时,如果没有做过异步处理,页面生命周期将会顺序执行,当引入子组件之后,父组件生命周期+子组件生命周期会交叉执行,具体执行顺序如下:

  • 页面加载:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
  • 数据更新:当父子不涉及传参时,父子数据更新单独触发,不会相互影响;当父子涉及传参时:父beforeUpdate ->子beforeUpdate ->子updated->父updated
  • 组件销毁:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
2.2.2 案例

我们看一下案例的完整代码,将上面的案例添加子组件代码,并将父组件的参数传给子组件,子组件参数传给父组件,页面如下:

在这里插入图片描述

父页面:

<template><div><title ref="myPage">生命周期</title><div>父页面:</div><div>参数:{{ parama }}</div><div>子参数:{{ childData }}</div><el-button @click="updateParama">更新父数据</el-button><el-button @click="leavePage">离开页面</el-button><child :param="parama" @childData="getChildData"></child></div>
</template>
<script>
import Vue from "vue";
import { Button } from "element-ui";
import child from "./conponents/child";
Vue.use(Button);export default {beforeCreate() {console.log("页面加载:beforeCreate");console.log("parama", this.parama);},created() {// await setTimeout({}, 1000);console.log("页面加载:created");console.log("parama", this.parama);},beforeMount() {console.log("页面加载:beforeMount");console.log("parama", this.parama);},mounted() {console.log("页面加载:mounted");console.log("parama", this.parama);},beforeUpdate() {console.log("页面更新:beforeUpdate");console.log("parama", this.parama);},updated() {console.log("页面更新:updated");console.log("parama", this.parama);},beforeDestroy() {console.log("页面销毁:beforeDestroy");console.log("parama", this.parama);},destroyed() {console.log("页面销毁:destroyed");console.log("parama", this.parama);},components: {child},data() {return {parama: 0,childData: 0};},methods: {leavePage() {this.$router.push({ path: "/home" });},updateParama() {this.parama++;},getChildData(a) {this.childData = a;}}
};
</script>

子组件:

<template><div class="child"><title>子组件</title><div>子组件:</div><div>参数:{{ paramaChild }}</div><div>父传参:{{ param }}</div><el-button @click="updateParama">更新子页面数据</el-button></div>
</template>
<script>
import Vue from "vue";
import { Button } from "element-ui";
Vue.use(Button);export default {beforeCreate() {console.log("子页面加载:beforeCreate");console.log("paramaChild", this.paramaChild);},created() {// await setTimeout({}, 1000);console.log("子页面加载:created");console.log("paramaChild", this.paramaChild);},beforeMount() {console.log("子页面加载:beforeMount");console.log("paramaChild", this.paramaChild);},mounted() {console.log("子页面加载:mounted");console.log("paramaChild", this.paramaChild);},beforeUpdate() {console.log("子页面更新:beforeUpdate");console.log("paramaChild", this.paramaChild);},updated() {console.log("子页面更新:updated");console.log("paramaChild", this.paramaChild);},beforeDestroy() {console.log("子页面销毁:beforeDestroy");console.log("paramaChild", this.paramaChild);},destroyed() {console.log("子页面销毁:destroyed");console.log("paramaChild", this.paramaChild);},props: {param: Number},data() {return {paramaChild: 0};},methods: {updateParama() {this.paramaChild++;this.$emit("childData", this.paramaChild);}}
};
</script>
<style>
.child {margin: 20px;border: 1px solid gray;
}
</style>
  • 页面加载

页面加载时,父子生命周期:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

控制台日志如下:

页面加载:beforeCreate
parama undefined
页面加载:created
parama 0
页面加载:beforeMount
parama 0
子页面加载:beforeCreate
undefined
子页面加载:created
paramaChild 0
子页面加载:beforeMount
paramaChild 0
子页面加载:mounted
paramaChild 0
页面加载:mounted
parama 0
  • 页面更新

页面更新的情况分两类;

一类是父子组件不涉及传参,这种情况下,父页面参数更新不会触发子组件更新的生命周期,子组件的数据更新也不会触发父页面的生命周期。

第二类是父子组件涉及传参,不管是父传子还是子传父,父子生命周期相同,都为:父beforeUpdate ->子beforeUpdate ->子updated->父updated。

在以上父子相互传参案例下,我们点击父页面按钮:更新父数据;以及点击子组件按钮:更新子页面数据;控制台打印顺序都如下:

页面更新:beforeUpdate
子页面更新:beforeUpdate
子页面更新:updated
页面更新:updated
  • 页面销毁

页面销毁

点击”离开页面“按钮:控制台打印顺序如下:

页面销毁:beforeDestroy
子页面销毁:beforeDestroy
子页面销毁:destroyed
页面销毁:destroyed
2.3 keep-alive生命周期

<KeepAlive> 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。被缓存的实例普通Vue实例相比,多了activateddeactivated两个生命周期。

  • activated:被 keep-alive 缓存的组件激活时调用。
  • deactivated:被 keep-alive 缓存的组件失活时调用。

光这两个生命周期来讲,就是组件激活和失活的时候调用,但直到这个是不够的,这两个生命周期与其他八个生命周期顺序是什么样子的呢,我们先说结论:(子组件被keep-alive包裹)

  • 页面加载:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->子activated(当子组件初次加载即可见时)->父mounted
  • 数据更新导致组件失活:父beforeUpdate ->子deactivated->父updated
  • 父页面销毁:父beforeDestroy->子deactivated(如果销毁前子组件是激活状态)->子beforeDestroy->子destroyed->父destroyed

我们结合案例来看实际情况;

2.3.1 案例

我们在案列中添加了一个组件来用于子组件的切换,并在子组件中添加activateddeactivated两个生命周期的监听函数,添加的代码如下:

父页面:

<template><div><title ref="myPage">生命周期</title><div>父页面:</div><div>参数:{{ parama }}</div><div>子参数:{{ childData }}</div><el-button @click="changeActiveCom">更换组件</el-button><el-button @click="leavePage">离开页面</el-button><keep-alive><component :is="activeComponent"></component></keep-alive></div>
</template>
<script>
import Vue from "vue";
import { Button } from "element-ui";
import child from "./conponents/child";
import comA from "./conponents/comA";
Vue.use(Button);export default {beforeCreate() {console.log("页面加载:beforeCreate");console.log("parama", this.parama);},created() {// await setTimeout({}, 1000);console.log("页面加载:created");console.log("parama", this.parama);},beforeMount() {console.log("页面加载:beforeMount");console.log("parama", this.parama);},mounted() {console.log("页面加载:mounted");console.log("parama", this.parama);},beforeUpdate() {console.log("页面更新:beforeUpdate");console.log("parama", this.parama);},updated() {console.log("页面更新:updated");console.log("parama", this.parama);},beforeDestroy() {console.log("页面销毁:beforeDestroy");console.log("parama", this.parama);},destroyed() {console.log("页面销毁:destroyed");console.log("parama", this.parama);},components: {child,comA},data() {return {parama: 0,childData: 0,activeComponent: "child"};},methods: {leavePage() {this.$router.push({ path: "/home" });},updateParama() {this.parama++;},getChildData(a) {this.childData = a;},changeActiveCom() {this.activeComponent = this.activeComponent == "comA" ? "child" : "comA";}}
};
</script>

子组件child:

  activated() {console.log("child组件激活:activated");},deactivated() {console.log("child组件失活:deactivated");},

页面效果:

在这里插入图片描述

点击**更换组件**按钮实现组件切换功能,用于触发activateddeactivated两个生命周期,

  • 页面加载

页面加载时打印结果如下:

页面加载:beforeCreate
页面加载:created
页面加载:beforeMount
子页面加载:beforeCreate
子页面加载:created
子页面加载:beforeMount
子页面加载:mounted
child组件激活:activated
页面加载:mounted
  • 页面更新

点击更换组件按钮其实就进行了data数据更新操作,如果组件进入不可见状态,此时控制台打印日志如下:

页面更新:beforeUpdate
child组件失活:deactivated
页面更新:updated

如果组件进入可见状态,控制台打印如下:

页面更新:beforeUpdate
child组件失活:activated
页面更新:updated
  • 页面销毁

点击离开页面按钮,父页面销毁,此时子组件页销毁,若是此时子组件处于激活状态,那么控制台打印如下:

页面销毁:beforeDestroy
child组件失活:deactivated
子页面销毁:beforeDestroy
子页面销毁:destroyed
页面销毁:destroyed

大家可以进行案例尝试;

2.4 其他

生命周期不是一成不变的,当生命周期执行异步操作时,生命周期的结束时间顺序就可能发生变化;如下我们在父组件created执行异步操作:

  async created() {await setTimeout({}, 1000);console.log("页面加载:created");},

此时控制台打印日志如下

页面加载:beforeCreate
页面加载:beforeMount
页面加载:mounted
页面加载:created

我们可以看到,created是最后执行完的;由此,要注意异步操作的生命周期变化。

若是我们在相应生命周期做了异步操作,并返回相应的值,在后续生命周期可能会出现无法渠道返回值的情况,这也是实际生产开发容易出问题的点。特别是生产情况客户手机网络较慢的情况下。

3 总结

掌握 Vue.js 的生命周期非常重要,因为它允许你在组件的不同阶段执行自定义的代码,从而实现对组件行为的精细控制和管理。但同时也有一些注意事项,以下也是一些注意事项:

  1. 避免在 render 函数中直接修改数据:render 函数中修改数据可能会导致不可预料的结果,因为 render 函数是用来生成虚拟 DOM 的,应该是一个纯函数。如果需要在渲染过程中修改数据,应该使用 computed 属性或者 watch 监听器。
  2. 谨慎使用异步操作: 在生命周期钩子函数中进行异步操作时,一定要注意异步操作完成的时机和影响。尤其是在 created 钩子中,当组件实例已经创建但是 DOM 还未挂载时,执行异步操作可能会导致一些问题。最好的做法是在 mounted 钩子中执行异步操作。
  3. 避免频繁使用 beforeUpdateupdated 虽然 beforeUpdateupdated 钩子提供了在组件更新时执行逻辑的机会,但频繁使用这些钩子可能会导致性能问题。应该仔细考虑哪些逻辑需要在组件更新时执行,以避免不必要的性能开销。
  4. 合理利用 activateddeactivated 钩子: 当使用 <keep-alive> 组件缓存组件时,activateddeactivated 钩子会被调用。在这些钩子中,你可以执行一些与组件缓存和恢复相关的逻辑,比如重置组件状态或重新加载数据。
  5. beforeDestroy 进行清理:beforeDestroy 钩子中,你可以执行一些清理工作,比如清除定时器、取消订阅、解绑事件等。这样可以确保在组件销毁前进行必要的资源释放,避免内存泄漏和其他问题。

http://www.mrgr.cn/p/17070338

相关文章

C#动态查询:巧用Expression组合多条件表达式

概述:在C#中,通过`Expression`类、`AndAlso`和`OrElse`方法可组合两个`Expression<Func<T, bool>>`,实现多条件动态查询。通过创建表达式树,可轻松构建复杂的查询条件。 在C#中,可以使用AndAlso和OrElse方法组合两个Expression<Func<T, bool>>类型…

《Decoupled Optimisation for Long-Tailed Visual Recognition》阅读笔记

论文标题 《Decoupled Optimisation for Long-Tailed Visual Recognition》 长尾视觉识别的解耦优化 作者 Cong Cong、Shiyu Xuan、Sidong Liu、Shiliang Zhang、Maurice Pagnucco 和 Yang Song、 来自新南威尔士大学计算机科学与工程学院、北京大学计算机学院多媒体信息处理国…

、、、、、

、、 Java虚拟机栈存储了Java方法调用时的栈帧&#xff0c;而本地方法栈存储的是native本地方法的栈帧。 这里的native本地方法、指的是&#xff1f; 在Java中&#xff0c;native本地方法指的是、使用其他语言&#xff08;如C、C等&#xff09;编写的方法&#xff0c;这些方…

Rocketmq 不同的topic要配不同的consumegroup

Rocketmq 不同的topic要配不同的consumegroup 使用Rocketmq一定要注意,如果项目中要订阅两个topic,一定要保证consumeGroup是两个不同的。这是因为,Consumer会定期发送心跳,默认是30s一次。心跳会像全部broker发送,心跳包内容包括groupname,topicname1。然后broker端会缓…

基于docker安装flink

文章目录 环境准备Flinkdocker-compose方式二进制部署 KafkaMysql Flink 执行 SQL命令进入SQL客户端CLI执行SQL查询表格模式变更日志模式Tableau模式窗口计算 窗口计算滚动窗口demo滑动窗口 踩坑 环境准备 Flink docker-compose方式 version: "3" services:jobman…

2022年windows的Visual Studio常用插件及使用手册

前景提要Viusual Studio 是一款很好用的C/C++集成开发工具,具有强大的扩展功能,好用的插件,但是,很多人都是只写了有什么插件,但是,没写怎么使用这种插件,使得使用的时候很是不方便,所以,笔者最近本着自己的学习,在这里写下自己关于好用的插件的研究,希望对您的学习/工作有帮助…

JDK源码阅读-------自学笔记(二十六)(java.util.Map 自定义讲解)

一、简介Map就是用来存储“键(key)-值(value)”对的. 通过键寻找value,所以键不能重复. 数组的本质也是一种键值对,区别就是索引一般是数字,而Map的Key可以是任意对象(字符串,数字),相当于把数组的索引范围扩的更大,使用更方便. 实际开发中较为常用.二、Map的常用方法实例(1)存…

华普检测温湿度监测系统建设方案

一、项目背景 随着医疗行业的蓬勃发展&#xff0c;药品、试剂和血液的储存安全直接关系到患者的健康。根据《药品存储管理规范》、《医疗器械冷链&#xff08;运输、贮存&#xff09;管理指南》、《疫苗储存和运输管理规范》和《血液存储要求》等相关法规&#xff0c;医院药剂…

Oracle修改字段长度及属性

首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247486117&idx=1&sn=02e2cd05e5db7eaa5758c70e81cf3972&chksm=ea375ed5dd40d7c367727562bdb00788f3bd139cbbda377f599586a47ce13ad9d04c56fd4d2d&token…

初识C语言——第十六天

C语言中的语句结构类型:顺序/选择/循环 分支语句 if else switch 循环语句 while for do whlie goto语句 代码练习:找两个整数的最大公约数和最小公倍数 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h>//int main() //{ // int age 60; // if (ag…

let命令

let 命令 let 与 var 二者区别&#xff1a; 作用域不同&#xff1a;变量提升&#xff08;Hoisting&#xff09;&#xff1a;临时性死区重复声明&#xff1a; 联系&#xff1a;举例说明&#xff1a; 块级作用域 块级作用域的关键字使用 var&#xff08;无块级作用域&#xff09;…

【数学建模】天然肠衣搭配问题衍生问题/线性规划限制条件建立问题

线性规划限制条件建立问题 前景回顾/提出问题回顾1回顾2/问题提出解决前提 解决方法坐标轴(区间)法总结 前景回顾/提出问题 回顾1 首先回顾一下DVD在线租赁问题 在 question2中&#xff0c;需要保证每个人都不会收到自己不喜欢的DVD&#xff0c;即客户在线订单数为0时候&…

C#/.NET/.NET Core优秀项目和框架2024年4月简报

前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项…

c#word文档:3.向Word文档中插入表格/4.读取Word文档中表格

--向Word文档中插入表格-- &#xff08;1&#xff09;在OfficeOperator项目的WordOperator类中定义向Word文档插入换页的函数NewPage &#xff08;2&#xff09;在WordOperator类中定义向Word文档插入表格的函数InsertTable using Microsoft.Office.Interop.Word;// 引入Mic…

30分钟彻底了解Flutter整个渲染流程(超详细)

30分钟彻底了解Flutter整个渲染流程[超详细] 从运行第一行代码出发WidgetsFlutterBinding初始化了一堆娃 三个中流砥柱SchedulerBindingRendererBindingWidgetsBinding 申请Vsync流程下发Vsync承接Vsync 从运行第一行代码出发 void main() {runApp(const MyApp()); }void runA…

linux中进程相关概念(一)

什么是程序&#xff0c;什么是进程&#xff0c;有什么区别&#xff1f; 程序是静态的概念&#xff0c;当我们使用gcc xxx.c -o pro进行编译时&#xff0c;产生的pro文件&#xff0c;就是一个程序。 进程是程序的一次运行活动&#xff0c;通俗点就是说程序跑起来了就是进程。 …

C++反汇编,指针和内存分配细节,面试题05

文章目录 20. 指针 vs 引用21. new vs malloc 20. 指针 vs 引用 指针是实体&#xff0c;占用内存空间&#xff0c;逻辑上独立&#xff1b;引用是别名&#xff0c;与变量共享内存空间&#xff0c;逻辑上不独立。指针定义时可以不初始化&#xff1b;引用定义时必须初始化。指针的…

Vue自定义封装音频播放组件(带拖拽进度条)

Vue自定义封装音频播放组件&#xff08;带拖拽进度条&#xff09; 描述 该款自定义组件可作为音频、视频播放的进度条&#xff0c;用于控制音频、视频的播放进度、暂停开始、拖拽进度条拓展性极高。 实现效果 具体效果可以根据自定义内容进行位置调整 项目需求 有播放暂停…

localhost 重定向次数过多

在完成javaweb作业时出现了错误初始页面只有两个功能, 但是无论是点击登录还是注册,都会跳转到login.jsp页面从网上找到的答案是代码陷入死循环,因为总是跳转到login.jsp, 所以我查看了所有servlet类中跳转到login.jsp页面的代码,逻辑上并没有问题;然后我又查看了过滤器以…

Windows平台使用CMake+MinGW64编译OpenCV

Windows平台使用CMake+MinGW64编译OpenCV (注:2年前写的笔记, 可能有些地方过时了) 目录Windows平台使用CMake+MinGW64编译OpenCV1.安装及配置环境1.1 MinGW-w641.2 CMake1.3 OpenCV源码2.CMake配置及生成2.1 新建目录2.2 CMake-GUI2.3 编译配置2.4 生成2.5 Make编译和安装3.配…