防抖函数(最全 最干净 最好理解)

文章介绍了JavaScript中的防抖(debounce)技术,主要用于优化高频场景如输入查询、验证等,防止重复渲染。通过调整函数执行时机,避免在短时间内多次触发同一操作。文中详细解释了防抖函数的实现,包括处理this问题、事件对象以及如何通过apply方法传递参数。并提供了一个具体的防抖函数实现示例,应用于鼠标移动事件以控制元素内容更新。

1.应用场景

1.input输入框 输入远程查询

2.邮箱,手机号验证,用户名验证

3.resize等高评率场景

2.解决问题

高频场景带来的重复渲染 等问题

多次操作 只在操作结束后再执行操作函数

3.具体实现

3.1this问题(因为settimeout是window的对象 所以函数中this指向window 除非箭头函数)

1.事件函数里的this才指向box

错误示范:return里面的this 与函数体内的this指向 在不同场景中 指向并不相同

解决方案: 可以使用 fun.call(this) 来重定向函数中this的指向 【call 传参是单个】

3.2事件对象

1.arguments 作为函数的关键字,它接收的是这个函数传递的所有实参,包括这个事件对象

因为事件对象是默认传递的参数,因为call只能传递一个参数,所以我们选择使用 fun.apply(this, args)


3.3具体代码实现
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        html {
            height: 100%;
        }
        body {
            display: flex;
            height: 100%;
            justify-content: center;
            align-items: center;
        }
        .box {
            width: 100px;
            height: 100px;
            text-align: center;
            line-height: 100px;
            background: #FF9800;
            color: #fff;
        }
    </style>
</head>
<body>
    <div class="box">  0 </div>
    <script>
        const debounce = function (fun, delay) {
            let timeout
            return function() {
                timeout && clearTimeout(timeout)
                // 解决事件函数绑定中this
                let that = this
                // 绑定事件中 事件函数的传递
                let argus = arguments
                timeout = setTimeout(() => {
                  fun.apply(that, argus)  
                }, delay);
            }
        }
        function fun(e) {
            count++
            console.log('>>>>>>>>>>>>', e, this)
            e.target.innerText = count
        }
        let count = 0
        console.log()
        document.querySelectorAll('.box')[0].addEventListener('mousemove', debounce(fun, 300))
    </script>
</body>
</html>

#include "stm32f10x.h" // Device header #include "motor.h" #include "Encoder.h" #include "AD.h" //高级定时器1初始化 //arr:自动重装值。 //psc:时钟预分频数 //这里使用的是定时器1! void TIM3_IRQHandler(void) //中断处理函数,此处为定时器3,10ms中断 { if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { Encoder_Get(); //得到编码器读数 guiyi(); adcwork(); weizhi_pid(); pid_1(20-dif); pid_r(20+dif); TIM_ClearITPendingBit(TIM3, TIM_IT_Update);// 清除中断标志 } } void TIM1_PWM_Init(uint16_t arr, uint16_t psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; /* 1. 开启时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); /* 2. 配置 PA8、PA9、PA10、PA11 为复用推挽输出 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* 3. 配置 TIM1 基本定时参数 */ TIM_TimeBaseStructure.TIM_Period = arr; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分割 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); /* 4. 配置 PWM 模式 */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM1 模式 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 高电平有效 TIM_OC1Init(TIM1, &TIM_OCInitStructure); // CH1 -> PA8 TIM_OC2Init(TIM1, &TIM_OCInitStructure); // CH2 -> PA9 TIM_OC3Init(TIM1, &TIM_OCInitStructure); // CH3 -> PA10 TIM_OC4Init(TIM1, &TIM_OCInitStructure); // CH4 -> PA11 /* 5. 启动定时器 */ TIM_Cmd(TIM1, ENABLE); /* 6. 高级定时器必须开启主输出才能有 PWM 波形 */ TIM_CtrlPWMOutputs(TIM1, ENABLE); } // 定时器3中断初始化函数 void TIM3_ISR_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; // 1. 使能TIM3时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 2. 定时器参数配置 TIM_TimeBaseStructure.TIM_Period = 10000 - 1; // 自动重装载值,定时周期 TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频,假设72MHz时钟,分频720后变100kHz TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分割,不分频 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 3. 配置NVIC中断分组和优先级 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 建议在主程序初始化时调用一次即可 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // TIM3中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断 NVIC_Init(&NVIC_InitStructure); // 4. 使能TIM3更新中断 TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 5. 使能定时器 TIM_Cmd(TIM3, ENABLE); } /************************************************************************** Function: Initialize TIM2 to encoder interface mode Input : none Output : none 函数功能:把TIM2初始化为编码器接口模式 **************************************************************************/ void TIM2_Encoder_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* 1. 使能时钟 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); // GPIOA, GPIOB和AFIO时钟 /* 2. 禁用JTAG,释放PA15和PB3 ,将TIM2的引脚重映射到PA15和PB3*/ GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE); /* 3. 配置PA15和PB3为上拉输入 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOB, &GPIO_InitStructure); /* 4. 定时器基本参数配置 */ TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Prescaler = 0; // 不分频 TIM_TimeBaseStructure.TIM_Period = 65535; // 大计数值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 不分频 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); /* 5. 编码器接口配置,模式3 */ TIM_EncoderInterfaceConfig( TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising ); /* 6. 输入捕获滤波器 */ TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 10; // 滤波10,防抖 TIM_ICInit(TIM2, &TIM_ICInitStructure); /* 7. 计数器清零并启动 */ TIM_SetCounter(TIM2, 0); TIM_Cmd(TIM2, ENABLE); } /************************************************************************** Function: Initialize TIM4 to encoder interface mode Input : none Output : none 函数功能:把TIM4初始化为编码器接口模式 **************************************************************************/ void TIM4_Encoder_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* 1. 使能时钟 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//使能定时器4的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能PB端口时钟 /* 2. 配置PB6和PB7为上拉输入 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入 GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB /* 3. 定时器基本参数配置 */ TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Prescaler = 0; // 预分频器 TIM_TimeBaseStructure.TIM_Period = 65535; //设定计数器自动重装值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;////TIM向上计数 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); /* 4. 编码器接口配置,模式3 */ TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3 /* 5. 输入捕获滤波器 */ TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 10; TIM_ICInit(TIM4, &TIM_ICInitStructure); /* 6. 计数器清零并启动 */ TIM_SetCounter(TIM4,0); TIM_Cmd(TIM4, ENABLE); } 这个是我的timer。c文件,#include "stm32f10x.h" // Device header #include "Encoder.h" #include "AD.h" int err=0; int dif=0; int err_old; float kp= 0.5 ; float kd= 0; void motor_control_L(int pwm_l) { if(pwm_l>=0) { TIM_SetCompare1(TIM1,0); TIM_SetCompare2(TIM1,pwm_l); } else if(pwm_l<0) { TIM_SetCompare1(TIM1,-pwm_l); TIM_SetCompare2(TIM1,0); } } void motor_control_R(int pwm_r) { if(pwm_r>=0) { TIM_SetCompare3(TIM1,0); TIM_SetCompare4(TIM1,pwm_r); } else if(pwm_r<0) { TIM_SetCompare3(TIM1,-pwm_r); TIM_SetCompare4(TIM1,0); } } float kp_1 = 4 ; float ki_1 = 1.6; int pwm_out_1,error_1,error_1_old; void pid_1(int expect_1) { error_1=expect_1-Encoder_Left; pwm_out_1+=kp_1*(error_1-error_1_old)+ki_1 * error_1; error_1_old=error_1; if(pwm_out_1>750) { pwm_out_1=750; } if(pwm_out_1<-750) { pwm_out_1=-750; } motor_control_L(pwm_out_1); } float kp_r = 4 ; float ki_r = 1.6; int pwm_out_r,error_r,error_r_old; void pid_r(int expect_r) { error_r=expect_r-Encoder_Right; pwm_out_r+=kp_r*(error_r-error_r_old)+ki_r * error_r; error_r_old=error_r; if(pwm_out_r>750) { pwm_out_r=750; } if(pwm_out_r<-750) { pwm_out_r=-750; } motor_control_R(pwm_out_r); } void weizhi_pid() { err=adcerror; dif=kp*err+kd*(err-err_old); if(dif>20) { dif=20; } if(dif<-20) { dif=-20; } err_old=err; } 这个是我的motor。c文件#include "stm32f10x.h" // Device header #include "math.h" uint16_t AD_Value[5]; //定义用于存放AD转换结果的全局数组 uint16_t L; // 左侧传感器归一化值 uint16_t L_M; // 左中侧传感器归一化值 uint16_t R_M; // 右中侧传感器归一化值 uint16_t R; // 右侧传感器归一化值 uint16_t ADC_min = 10; // 传感器无信号时的小ADC值(需实测) uint16_t ADC_max = 2000; // 传感器在赛道上的大ADC值(需实测) int32_t fenzi; uint32_t fenmu; int16_t adcerror; // 终输出的转向偏差(范围-30~30) /** * 函 数:AD初始化 * 参 数:无 * 返 回 值:无 */ void AD_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA1的时钟 /*设置ADC时钟*/ RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0、PA1、PA2和PA3引脚初始化为模拟输入 /*规则组通道配置*/ ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //规则组序列2的位置,配置为通道1 ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 3, ADC_SampleTime_55Cycles5); //规则组序列3的位置,配置为通道4 ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 4, ADC_SampleTime_55Cycles5); //规则组序列4的位置,配置为通道6 ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 5, ADC_SampleTime_55Cycles5); //规则组序列4的位置,配置为通道7 /*ADC初始化*/ ADC_InitTypeDef ADC_InitStructure; //定义结构体变量 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换,使能,每转换一次规则组序列后立刻开始下一次转换 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定 ADC_InitStructure.ADC_NbrOfChannel = 5; //通道数,为4,扫描规则组的前4个通道 ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1 /*DMA初始化*/ DMA_InitTypeDef DMA_InitStructure; //定义结构体变量 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设基地址,给定形参AddrA DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址自增,选择失能,始终以ADC数据寄存器为源 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存储器基地址,给定存放AD转换结果的全局数组AD_Value DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //存储器数据宽度,选择半字,与源数据宽度对应 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能,每次转运后,数组移到下一个位置 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组 DMA_InitStructure.DMA_BufferSize = 5; //转运的数据大小(转运次数),与ADC通道数一致 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //模式,选择循环模式,与ADC的连续转换一致 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等 DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1 /*DMA和ADC使能*/ DMA_Cmd(DMA1_Channel1, ENABLE); //DMA1的通道1使能 ADC_DMACmd(ADC1, ENABLE); //ADC1触发DMA1的信号使能 ADC_Cmd(ADC1, ENABLE); //ADC1使能 /*ADC校准*/ ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准 while (ADC_GetResetCalibrationStatus(ADC1) == SET); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1) == SET); /*ADC触发*/ ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作 } void guiyi(void) { R = (uint16_t)(((uint32_t)(AD_Value[3] - ADC_min) * 100) / (ADC_max - ADC_min)); R_M = (uint16_t)(((uint32_t)(AD_Value[2] - ADC_min) * 100) / (ADC_max - ADC_min)); L_M = (uint16_t)(((uint32_t)(AD_Value[1] - ADC_min) * 100) / (ADC_max - ADC_min)); L = (uint16_t)(((uint32_t)(AD_Value[0] - ADC_min) * 100) / (ADC_max - ADC_min)); if (R > 100) R = 100; if (R_M > 100) R_M = 100; if (L_M > 100) L_M = 100; if (L > 100) L = 100; } void adcwork(void) { fenzi = 60 *( (L - R) + 3 * (L_M - R_M)); fenmu = (L + R) + 3*(L_M + R_M) + 1; adcerror = (int16_t)(fenzi / fenmu); if (adcerror > 30) { adcerror = 30; } else if (adcerror < -30) { adcerror = -30; } } 这个是我的adc。c文件#include "stm32f10x.h" // Device header #include "timer.h" int Encoder_Left=0; int Encoder_Right=0; /************************************************************************** Function: Read encoder count per unit time Input : TIMX:Timer Output : none 函数功能 :单位时间读取编码器计数 返 回 值 :速度值 **************************************************************************/ int Read_Encoder_L (void) { int Encoder_TIM_L; Encoder_TIM_L=TIM2->CNT; //读取计数 if(Encoder_TIM_L>0xefff)Encoder_TIM_L=Encoder_TIM_L-0xffff; //转化计数值为有方向的值,大于0正转,小于0反转。 //TIM4->CNT范围为0-0xffff,初值为0。 TIM2->CNT=0; //读取完后计数清零 return Encoder_TIM_L; //返回值 } int Read_Encoder_R (void) { int Encoder_TIM_R; Encoder_TIM_R=TIM4->CNT; //读取计数 if(Encoder_TIM_R>0xefff)Encoder_TIM_R=Encoder_TIM_R-0xffff; //转化计数值为有方向的值,大于0正转,小于0反转。 //TIM4->CNT范围为0-0xffff,初值为0。 TIM4->CNT=0; //读取完后计数清零 return Encoder_TIM_R; //返回值 } void Encoder_Get(void) { Encoder_Left=Read_Encoder_L(); //读取左轮编码器的值,前进为正,后退为负 Encoder_Right=Read_Encoder_R(); //读取右轮编码器的值,前进为正,后退为负 } 这个是我的encorder。c文件为什么我的stm32f103c8t6为核心板的电磁循迹智能小车不能循迹,只会乱跑
最新发布
11-12
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值