木灵鱼儿
阅读:1192
搞明白防抖与节流
防抖
防抖在日常项目开发的时候还是很重要的,用于防止函数的重复触发,节省性能开支。
比如监听用户的input输入进行api查询,由于input事件每次改变输入的内容都会触发,但是一般来说,只有最后一次input才有效,所以会经常采用防抖的形式来触发。
防抖:在一定时间内重复触发之后只会生效最后一次。
如何写一个防抖呢,我们慢慢来
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function() {
console.log(`触发了:${Date.now()}`, this);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(fn, delay)
}
} <
/script>
创建一个debounce
函数,函数内新增一个timer
变量,用于存放定时器,然后抛出一个新函数,函数里面判断,如果定时器存在,就先清除定时器,重新赋值一个定时器。
这样一个基本的雏形就有了,剩下的就是要考虑函数this,以及参数的问题了。
this指向
函数内运行的函数,其this指向window对象,如果在严格模式下,this就是undefined。
function fn1() {
function fn2() {
console.log(this)
}
fn2();
}
fn1(); //window,严格模式下undefined
但是我们的防抖,是将函数return出去,所以,按照防抖的写法,return出去的函数直接绑定再input对象上,那么它的this指向的就是input本身。
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function() {
console.log(`触发了:${Date.now()}`, this);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function() {
console.log(this); //input
if (timer) clearTimeout(timer);
timer = setTimeout(fn, delay)
}
}
< /script>
但是我们实际的函数fn却是被setTimeout
调用,setTimeout存在于window对象上,fn通过作用域链往上查询this,最终找到的是window,this就等于window。
在函数中,this的指向完全取决于函数调用的位置;谁调用它,他就指向调用它的那个对象。
那么,既然能在return的函数中正确的获取到this,那么我们就可以有很多种方式让fn绑定到正确的this上。
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function() {
console.log(`触发了:${Date.now()}`, this);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(fn.bind(this), delay)
}
}
</script>
我们可以用bind或者call,apply的方式改写this指向。
你甚至可以看到防抖中这种写法:
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function() {
console.log(`触发了:${Date.now()}`, this);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function() {
let that = this
if (timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn.call(that);
}, delay)
}
} <
/script>
利用that变量保存,然后在箭头函数里面用call这种方式,事实上其实没必要的,箭头函数本身没有this,他的this将是声明它时作用域链中的this,那么this也会是input,因为他会找到return function它的this。
现在this,问题我们高定了,剩下的就是参数问题了。
参数
事件绑定,一般都是会使用到event对象,甚至一些其他自定义参数,我们不能确定使用者它将使用什么参数名,多少个参数,所以,我们得有一个办法获取到函数接收的所有参数。
在es5的时候,我们会使用函数内的arguments
对象,他是一个类数组对象,里面包含了所有的参数,但是并不建议使用,不过我也是道听途说,说是会影响性能。既然都es6了,可以放弃了。
现在es6的话,我们可以利用函数的reset
参数,将所有参数保存到数组中去。
function fn(...args) {
console.log(args); //[...]
}
那么现在参数可以拿到了,如何传给fn呢,我们bind方法其实提供了第二个参数,他和call的第二个参数一样,传递参数用的,所有参数用逗号分割。所以
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function(evnet) {
console.log(`触发了:${Date.now()}`, this, evnet);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(fn.bind(this,...args), delay)
}
}
</script>
一个防抖函数就完成了。
注意事项
由于防抖生成的是全新的函数,如果绑定了事件,想要关闭事件,那么就必须先将防抖生成的函数变量保存下来才能成功清除事件。
<script>
"use strict"
const input = document.querySelector("input");
const button = document.querySelector("button");
const fn = debounce(function(event) {
console.log(`触发了:${Date.now()}`, this, event);
}, 300);
input.addEventListener("input", fn);
button.addEventListener("click", function(event) {
input.removeEventListener("input", fn);
});
function debounce(fn, delay) {
let timer = null;
return function(...args) {
console.log(this);
if (timer) clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...args), delay)
}
}
</script>
节流
节流和防抖的处理完全不一样,节流是在一定时间内只触发一次,不管点击多少次。
用一个示意图来表明两者之间的区别:
画的比较糙,但是一眼就能看明白,节流在指定时间内触发一次,而防抖只触发最后一次。
function throttle(fn, delay) {
let timer = null;
return function(...args) {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
};
}
当然还有一些简单的写法,比如下面这种:
function throttle(fn, delay) {
let lastTime = null;
return function(...args) {
const newTime = Date.now();
if (!lastTime || newTime - lastTime > delay) {
fn.call(this, ...args);
lastTime = newTime;
}
}
}
这种写法会有一个bug,就是最后一次没法触发,除非触发它刚好在delay的时间内,所以也不是很建议使用,是我之前自己理解的一种写法,作为经验贴在这吧!
注意事项
由于会生成新的函数,所以事件绑定的时候,记得用变量保存,方便正确清理事件。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
Promise失败重试,可指定重试次数
//模拟异步请求 function axiosFn() { return new Promise((resolve, reject) => { const flge = Math.random(); //随机值 setTimeout(() => { //大于0.7就是成功 if (flge > 0.7) { return resolve(flge); } else { return reject(...
animation 动画的三个事件
const div = document.querySelector("div.box"); div.addEventListener("animationstart", function() { //动画开始运行触发 }); div.addEventListener("animationiteration", function() { //动画每执行一次触发一次,适用用多次动画 }); div.addEventListener("animationend", function() { //...
手写Promise
/* * @Author: mulingyuer * @Date: 2021-12-30 22:06:58 * @LastEditTime: 2022-01-03 05:22:30 * @LastEditors: mulingyuer * @Description: 手写promise * @FilePath: \undefinedc:\Users\13219\Desktop\promise.js * 怎么可能会有bug!!! */ /** * @description: 自定义promise * @param {fucntion} executor 执行器函数(同...
深度合并对象的方法
找了很久,现有的库有两个:1. Mergenpm地址: Merge用法:import merge from 'merge'; const obj1 = { name: 2}; const obj2 = { value: 1 }; //合并 merge(obj1,obj2); console.log(obj1); //{name:2,value:1} //克隆合并-与目标对象无关联 const obj3 = merge(true,obj1,obj2); console.log(obj3); //{name:2,value:1} console.log(obj3 === obj1)...
利用JSON过滤对象和数组中指定的key属性
有时候我们在vue中进行for循环,就会涉及到绑定唯一值key的问题,但是并不是任何时候都会存在所谓的唯一值,使用index下标明显是不合适的,官方也不推荐,除非你for循环出来的列表不用变化。所以一般常用的做法就是给for循环的对象添加一个属性,属性的值是随机的uuid或者时间戳。这样前端问题解决了,如果遍历的数据还需要提交到后端,那么不就多了一个属性,这个属性后端不需要的。所以,我们需要在提交数据前,对数据进行过滤。过滤又得for循环删除?那怎么行,有没有那种通用的,简单的方法。过滤方法/** * @description: 过滤对象中指定的属性,也可以拿来浅拷贝 * @para...
Copy 一个复制操作的类
前言js有一个31k多的star的开源复制库:clipboard.js;但是一些简单复制并不想安装一个库来解决,所以就想自己写一个。copy所需要的东西Selection 对象用于获取被用户选中的部分,通过toString()方法可以获取被选中的文本内容,以及js操作选中。MDN文档:SelectionexecCommand 对象用于以命令的形式来操作网页的内容,说白了就是用它来实现复制文本操作,复制的是选中的文本MDN文档:execCommand 需要注意的是,execCommand在未来将会被遗弃,因为这个api本身是从ie浏览器那边继承的,久而久之各大浏览器都对其做了兼容,虽然...
对象扁平化
前言后端返回给前端的数据,有时候会是一个多层级对象,但是我们前端使用的时候,for循环遍历渲染时,多层级对象往往需要进行单独处理,因为还需要判断这个key值是否存在,否则会报错。强行让后端改变数据结构又好像不现实,无奈,只有自己处理了。掘金看到一位大佬文章《【算法】JS 实现对象的扁平化》感觉很合适,逻辑清晰。要求将对象中的层级扁平化,改成如下格式:// 实现一个 flatten 函数,实现如下的转换功能 const obj = { a: 1, b: [1, 2, { c: true }], c: { e: 2, f: 3 }, g: null, }; // 转换为 l...
javascript 生成随机密码,指定位数,难度(大小写、数字、特殊字符)
原理也没啥好藏着掖着的,就是为了保证密码难度,开头的字符一定是满足安全需要的,比如要求大小写数字加特殊字符,那么开头四位就一定是:大写一位、小写一位、数字一位、特殊字符一位;然后剩下的随机。/** * @description: 随机密码 * @param {*} len 密码位数 * @param {*} mode 密码难度:hide(大小写数字特殊字符)、medium(大小写数字)、low(小写数字) * @Date: 2021-07-02 15:52:32 * @Author: mulingyuer */ export const randomPass = functi...
base64转file文件方法
使用canvas操作内容后,如果想保存图片之类的操作,就无可避免的要处理这一步,因为canvas导出的是base64格式的文件,如果你只做预览还好,存储的话,就要想办法了。我的想法是转成上传文件的那种file格式。方法如下:/** * @description: 将base64转换为文件对象 * @param {*} dataUrl base64 * @param {*} fileName 文件名 * @Date: 2021-06-30 14:33:47 * @Author: mulingyuer */ export function dataURLtoFile(dataUr...