js 防抖 节流 call apply 知识总结
最近学前端,在B站看到了防抖与节流的概念,做个总结(自己理解,可能有误,仅供参考,仅供参考,仅供参考).
防抖与节流概述
防抖: 在执行方法之后,方法并不会真正的执行,在规定的时间内没有再重复执行,这个方法才被真正的执行.
防抖应用场景: input框输入关键字,输入一个字等待一定时间没有再输入后,根据当前输入内容,请求后端接口数据,做内容联想补全提示
节流: 在执行方法后,方法会立刻执行,但是在规定的时间内,方法无法再次执行(类似于技能CD冷却)
节流应用场景: 监听页面滚动,原本的滚动监听方法,滚一下会执行几十到上百次,效率太高,就需要设置时间间隔让它冷却.
实现
其中用到的知识点: 闭包 箭头函数this指向 call
箭头函数
简单来说就是匿名函数优化简写了,但是不能完全随心所欲使用
function(value){
return value.length;
}
//变成
value => value.length
简写只是一种优点,更多的是解决匿名函数的this指向问题,但这也是一个坑
普通匿名函数this指向他的执行者
,箭头函数的this直接就是父级的this
例子
const aa = {
a(){
setTimeout(()=>{console.log(this);},1000) //{a: ƒ}
setTimeout(function(){console.log(this);},1000) //Window
}
}
aa.a()
所以箭头函数不能通过call来改变this的指向
call apply bind
简单来说call 和 apply是一样的,只有稍微传参差别都是改变this的指向
bind是直接返回一个绑定this之后的新方法,bind绑定的方法无法再次修改this指向
window.status = 'Windows的状态'
function setStatus(status1,status2){
this.status += `附加${status1}${status2}`
console.log(this.status);
}
const person = {
status : 'person状态',
getStatus:()=>{console.log(this.status);},
getStatus2(){console.log(this.status);},
getStatus3:function(){console.log(this.status);}
}
console.log(person); // {status: 'person状态'}
//直接执行,this就找到顶级作用域window了
setStatus('晕眩','致盲') // Windows的状态附加晕眩致盲
//绑定到了person,this就是person
setStatus.call(person,'晕眩','致盲') // person状态附加晕眩致盲
//apply相较于call只是传参不同
setStatus.apply(person,['晕眩','致盲']) // person状态附加晕眩致盲附加晕眩致盲
//把方法绑定到person后返回一个方法供调用
let setPersonStatus = setStatus.bind(person);
//调用传参
setPersonStatus('治疗','减伤'); // person状态附加晕眩致盲附加晕眩致盲附加治疗减伤
//重复多次调用
setPersonStatus('治疗1','减伤1'); // person状态附加晕眩致盲附加晕眩致盲附加治疗减伤附加治疗1减伤1
console.log(person); //{status: 'person状态附加晕眩致盲附加晕眩致盲附加治疗减伤附加治疗1减伤1'}
//箭头函数this对比
//箭头函数
person.getStatus() // Windows的状态附加晕眩致盲
//简写的正常函数
person.getStatus2() // person状态附加晕眩致盲附加晕眩致盲附加治疗减伤附加治疗1减伤1
//正常函数
person.getStatus3() // person状态附加晕眩致盲附加晕眩致盲附加治疗减伤附加治疗1减伤1
利用这个call取巧操作 仅作理解示例,真是应用起来某些可能脱裤子xx
比如document.querySelectorAll('li')
这样选出了一个NodeList,里面没有自带的slice方法做切割,可以借用数组里面的slice方法来切割NodeList.
//获得nodelist
let nodeList = document.querySelectorAll('li')
console.log(nodeList); //NodeList(5) [li, li, li, li, li]
//借用数组的slice方法掐头去尾切割nodelist
let newLiArr = [].slice.call(nodeList,1,-1)
console.log(newLiArr); //(3) [li, li, li]
//甚至这样直接转数组
let liArr = [].slice.call(nodeList)
console.log(liArr); // (5) [li, li, li, li, li]
//给nodelist添加数组的slice方法 bind时方法直接就把this绑定死了,后面无法修改
nodeList.slice = [].slice.bind(nodeList) //正常的话相当于 nodeList.slice = [].slice 但此处是硬绑定this指定死了是nodeList之后再调用call也无法修改this指向
//正常切割
console.log(nodeList.slice(1, -1)); //(3) [li, li, li]
//获取a标签nodelist
let a = document.querySelectorAll('a');
console.log(a); //NodeList(5) [a, a, a, a, a]
//此处借用上面nodelist的slice方法切割a,但还是返回的切割结果是上面nodelist自己的,所以bind之后无法再次修改this指向
console.log(nodeList.slice.call(a,1,-1)); //[li, li, li]
a.slice = nodeList.slice
//直接方法赋值也无法修改
console.log(a.slice(1,-1)); //[li, li, li]
//再次bind也无用
a.slice = nodeList.slice.bind(a)
console.log(a.slice(1,-1)); //[li, li, li]
数组合并
let a = [1, 2, 3];
let b = [4, 5, 6];
[].push.apply(a, b); // 相当于 a.push(4,5,6);
[].push.call(a, ...b);// 相当于 a.push(4,5,6);
console.log(a); //(9) [1, 2, 3, 4, 5, 6, 4, 5, 6]
//把数组的push方法绑定到a上之后返回 arrPush就是a.push
let arrPush = [].push.bind(a);
arrPush(7, 8, 9); // a.push(7,8,9);
console.log(a); // (12) [1, 2, 3, 4, 5, 6, 4, 5, 6, 7, 8, 9]
防抖实现
//fun 真实执行的函数 delay延时时间
function fangdou(fun, delay) {
//设置一个初始值
let timer = null;
//返回一个函数 此处也老老实实用function 不能用箭头函数 以确保里面的this是真实执行者
return function(){
//如果不是初始值,那就肯定是定时器,就清掉
if (timer !== null){
clearTimeout(timer);
}
//重新设置定时器
timer = setTimeout(()=>{
//设置this指向真实执行者
fun.call(this);
},delay)
}
}
//选中input框,添加一个keyup的事件监听 此处不能使用箭头函数,老老实实的用function 这样this才受控,上面能用call改变指向
document.querySelector('input').addEventListener('keyup',fangdou(function(){
console.log(this.value);
},1000))
上面代码就是输入框输入文字后停顿1秒 会打印输入的内容.
addEventListener会在最开始解析的时候就执行了fangdou方法以获取结果值,是一个function,然后再每次触发监听事件的时候再执行这个结果值.
就像
function say(){
console.log('say');
}
function callSay(){
return function(){
console.log('say');
}
}
function callSay2(){
return say;
}
//一开始就执行 延时未生效
setTimeout(say(),1000);
//以下正常延时1秒输出
setTimeout(say,1000);
setTimeout(callSay(),1000);
setTimeout(callSay2(),1000);
以前我用setTimeout踩过坑,延时不生效
节流实现
大同小异 此处没有this指向需求就简写使用箭头函数了
function jieliu(fun, delay) {
//定义是否可以允许
let run = true
return () => {
if (run) {
//执行方法
fun();
//设置不可运行
run = false;
//设置定时器延时delay之后改为可以运行
setTimeout(() => { run = true }, delay)
}
}
}
//添加页面上下滚动监听事件 cd冷却1秒
window.addEventListener('scroll', jieliu(() => {
console.log((new Date).toUTCString());
}, 1000))