防抖与节流
概述
- 防抖和节流是针对响应跟不上触发频率这类问题的两种解决方案
- 鼠标移动事件onmousemove, 滚动滚动条事件onscroll,窗口大小改变事件onresize,搜索框智能联想,瞬间的操作都会导致这些事件会被高频触发。
- 如果事件的回调函数较为复杂,就会导致响应跟不上触发,出现页面卡顿,假死现象。而且用户体验差,加载慢,费流量,太多请求增大服务器压力。
对比防抖节流
- 防抖:如果一直按下键盘,中间的状态都会被忽略,只执行最后一次
- 节流:如果一直按下键盘,每隔指定的时间执行一次
- 防抖:当降频函数被调用时,不立刻去执行处理函数,而是延迟时间t执行
- 节流:一件事如果执行的频率非常快,节流就是把频率降至指定的值
- 防抖:例如搜索框输入,智能联想,依旧可以发多次ajax请求,只不过是频率变慢了
- 节流:提交按钮,点击多次,只执行一次
函数防抖(debounce)
简述:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
<!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>防抖</title> </head> <body> <input id="input" type="text"> <script> function doSomething() { let val = document.getElementById('input').value console.log(val); } // 防抖 debounce // 降频函数 let timerId = null function debounceDosomething() { // 取消上一次的定时器 clearTimeout(timerId) // 3s执行一次doSomething // 开启新的定时器 timerId = setTimeout(() => { // 将需要执行的函数放在降频函数中,将降频函数放在调用处 doSomething(); }, 3000); } document.getElementById('input').addEventListener('input', () => { // doSomething(); debounceDosomething() }) </script> </body> </html>
图解函数防抖
函数防抖的封装
<!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>防抖封装</title> </head> <body> <input id="input" type="text"> <script> function doSomething() { let val = document.getElementById('input').value console.log(val); } // 防抖 debounce // 防抖函数(fn, 延时时间)——> 全新的函数 // 闭包 + 高阶函数 // 闭包:在内层函数f中,用外层定义的变量:timerId,fn,t // 高阶函数:如果一个函数f的参数或者返回值是函数,则称这个函数f是高阶函数 function debounce(fn, time) { let timerId = null function f() { // 取消上一次的定时器 clearTimeout(timerId) // 开启新的定时器 timerId = setTimeout(() => { // 将需要执行的函数放在降频函数中,将降频函数放在调用处 fn(); }, time); } return f } // 这里将debounce()赋值一个给debounceDoSomething // 是因为debounce(doSomething, 3000) return出去了一个函数,将这个函数赋值给debounceDoSomething // 然后在下面47行调用 // 如果不赋值的话,直接在下面debounce(doSomething, 3000),就是直接写return出去的那个函数,没有调用 const debounceDoSomething = debounce(doSomething, 3000) document.getElementById('input').addEventListener('input', () => { // doSomething(); debounceDoSomething() // debounce(doSomething, 3000) }) </script> </body> </html>
补存
- 变量的作用域
- 用var关键字在函数中声明的变量拥有函数作用域,只有在该函数内部才能访问到这个变量。
- 变量的生命周期
- 对于用var关键字在函数中声明的变量来说,退出函数时,这些变量就失去了它们的价值,它们会随着函数调用的结束而被销毁。
- 如果一个拥有函数作用域的变量,能够被外界访问,并且没有随着函数调用的结束而被销毁,在这里就产生了一个闭包。
闭包
- 闭包就是在函数内部可以访问到函数外部的参数和变量的组合
- 闭包是指有权访问另一个函数作用域中的变量的函数。
- 闭包的本质就是在一个函数内部创建另一个函数。
- 闭包3个特性
- 函数嵌套函数,被嵌套函数就是一个闭包,它会被return出去
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
闭包的原理
在一个函数内部嵌套的子函数会将其父函数的作用域添加到它的作用域链中,当父函数执行完毕之后,由于子函数的作用域链依然在引用它的某些变量或函数,所以父函数的作用域不会被摧毁。
图示
应用闭包的场合
- 定时器
for (var i = 0; i<5; i++) {
settimeout((function(){
console.log(i);
})(i),1000*i)
}
// 输出 0 1 2 3 4 5 - 闭包可以把一些不想要暴露在全局的变量或方法封装在函数内部。
- 函数防抖
// 函数防抖 ——》 属于闭包
export const debounce = function (fn, time) {
let timerId = null
function f() {
// 取消上一次的定时器
clearTimeout(timerId)
// 开启新的定时器 timerId = setTimeout(() => { // 将需要执行的函数放在降频函数中,将降频函数放在调用处 fn(); }, time); } return f // f就是一个闭包 }
- replace(reg,fn)
- 很多回调函数都是闭包
内存泄漏问题
高阶函数
直接引用的封装
// 防抖 debounce // 防抖函数(fn, 延时时间)——> 全新的函数 // 闭包 + 高阶函数 // 闭包:在内层函数f中,用外层定义的变量:timerId,fn,t // 高阶函数:如果一个函数f的参数或者返回值是函数,则称这个函数f是高阶函数 export const debounce = function (fn, time) { let timerId = null function f() { // 取消上一次的定时器 clearTimeout(timerId) // 开启新的定时器 timerId = setTimeout(() => { // 将需要执行的函数放在降频函数中,将降频函数放在调用处 fn(); }, time); } return f }
函数节流(throttle)
概述
- 当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
- 一件事如果执行的频率非常快,节流就是把频率降至指定的值
函数节流封装
<!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>节流</title> </head> <body> <input id="input" type="text"> <script> function doSomething() { let val = document.getElementById('input').value console.log(val); } // 输入一个函数及要求做节流处理时间频率 // 返回一个具备节流效果的函数 // 封装 function throttle(fn, t) { let timerId = null function throttleDoSomething() { if (timerId) { return } timerId = setInterval(() => { fn() timerId = null }, t); } return throttleDoSomething } const throttleDoSomething = throttle(doSomething, 500) document.getElementById('input').addEventListener('input', () => { // doSomething(); throttleDoSomething() }) </script> </body> </html>
函数节流项目图解
直接引用的封装
// 输入一个函数及要求做节流处理时间频率 // 返回一个具有节流效果的函数 export const throttle = function (fn, t) { let timerId = null function throttleDoSomething () { if (timerId) { return } timerId = setTimeout(() => { fn.apply(this) timerId = null }, t) } return throttleDoSomething }
补存
call、apply 和 bind 的实现以及区别
简述
在 JavaScript 中,call、apply 和 bind 是 function 对象自带的三个方法,这三个方法的主要作用就是改变函数中的this指向。
格式
- fun.call( thisArg,arg1,arg2,… )
- fun.apply(thisArg,[arg1,arg2,… ]) 其他参数必须放在一个数组里
- fun.bind( thisArg,arg1,arg2,…)第一个参数thisArgs 的取值有以下四种情况
- 非严格模式
1、不传,或者传null、undefined,函数中的this指向window
2、传递另一个函数的函数名,函数中的this就指向传入这个函数的this
3、传递字符串、数值或布尔值等基础类型,函数中的this指向其对应的包装对象
4、传递一个对象,函数中的this指向这个对象 - 严格模式
第一个参数是谁,this就指向谁,包括null和undefined,如果不传参数,this就是undefined
- call 中的细节
- 非严格模式
2、严格模式
- apply中的细节
- apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。
- apply 可以接受一个数组作为参数输入, call 则是接受一系列的单独变量
- call和apply可用来借用别的对象的方法
- apply对比call
使用
- bind中的细节
- 和call很相似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。这个和函数的防抖节流有点儿像,内部是return出去一个函数,调用的时候必须将外部函数赋值于一个变量,然后通过这个变量加()才能执行。
- bind:语法和call一模一样,区别在于立即执行还是等待执行
对比总结
- apply 和 call 的唯一区别就是第二个参数的传递方式不同,apply的第二个参数必须是一个数组(或者类数组),而call允许传递一个参数列表。
- apply 和 call 都是直接调用函数并得到函数执行结果,而 bind 会返回待执行函数,需要再次调用。
- call和apply可用来借用别的对象的方法
防抖节流闭包call/apply/bind都是比较重要的,用了两天时间查阅资料结合项目,总结了一下,能力有限,有错见谅,欢迎补存。部分图来源于网络,侵权删。