防抖和节流的使用场景
防抖(debounce)和节流(throttle)是优化高频触发事件的技术,它们可以提高性能,避免不必要的计算和函数执行。以下是一些实际场景的实例:
防抖(debounce):防抖用于确保一个函数在一定时间内只触发一次。它在短时间内多次触发同一个事件时,会取消之前的触发,直到最后一次触发后的一定时间间隔内没有新的触发才执行函数。常见的应用场景包括:
- 输入框实时搜索:当用户在输入框中输入时,可以使用防抖技术延迟执行搜索查询,减少不必要的查询和服务器压力。
- 窗口大小调整:当用户调整浏览器窗口大小时,可以使用防抖技术避免在调整过程中频繁地重新计算布局。
- 表单验证:当用户在表单输入时,可以使用防抖技术在用户停止输入一段时间后再执行验证,减少验证的次数。
节流(throttle):节流用于确保一个函数在一定时间内最多只触发一次。它会在触发事件期间以固定的时间间隔执行函数,而不管事件触发得多频繁。常见的应用场景包括:
- 滚动事件监听:例如监听页面滚动到底部加载更多数据时,可以使用节流技术减少检查滚动位置的频率,提高性能。
- 鼠标移动事件:例如实现一个拖拽功能,可以使用节流技术减少鼠标移动事件的处理频率。
- 动画效果:当实现一个基于时间的动画效果时,可以使用节流技术限制动画帧率,降低计算开销。
防抖实例:
// @param {Function} func 需要防抖的函数
// @param {Number} wait 防抖时间
// @param {Boolean} immediate 是否立即执行
const btn = document.getElementById('btn')
function debounce(func, wait, immediate) {
// 首先先进行判断
// 判断func是否是一个函数 如果不是函数的话就返回类型错误
if (typeof func !== 'function') throw new TypeError('Expected a function')
// 判断wait是否是一个数字 如果不是数字的话就给wait一个300
if (typeof wait !== 'number') wait = 300
// 判断immediate是否是一个布尔值 如果不是布尔值的话就给immediate一个false 表示不立刻执行
if (typeof immediate !== 'boolean') immediate = false
// 如果wait是一个布尔值的话
if (typeof wait === 'boolean') {
immediate = wait //
wait = 300
}
// 防抖函数 -》开启一个延时器, 让 func 在规定的时间内执行
// 采用高阶函数作为返回值,利用闭包的特性
// 这个timer在每次执行事件的时候都会在各个的延时器中进行引用,在函数外部进行声明
var timer;
// 返回一个函数
return function (...arg) {
// this
var that = this;
// console.log(that);
// 只有在timer被赋值,也就是事件已经触发过才会存在再点击对其进行一个函数打断过程
timer && clearTimeout(timer)
immediate && !timer ? func.apply(that,arg):null
// 当事件触发后就开启定时器
timer = setTimeout(function(){
// 这里的timer清空是为了实现立即触发后的事件仍然有一个定时器,在这个定时器定义的时间内点击不会再次执行而是
timer = null
// 因为定时器里面的this是window,所以需要进行一个指向 或者可以使用箭头函数
!immediate ? func.apply(that,arg):null
}, wait)
// timer = setTimeout(() => {
// console.log(this);
// func.apply(this,arg)
// },wait)
}
}
// 立即执行
// debounce(click, 1000, true)
// 非立即执行
// 防抖
// 函数引用
// btn.onclick = click
btn.onclick = debounce(click, 1000, false)
// 任何事件的都会默认有一个参数e
function click(e) {
// console.log(this);
// console.log(e);
console.log(1);
}
节流代码:
function scroll() {
console.log('scroll')
}
function myThrottle(fnc, wait) {
if (typeof fnc !== 'function') throw new Error('fnc must be a function')
if (typeof wait === 'undefined') wait = 500
let previous = 0
let timer= null
return function (...args) {
const that = this
const now = new Date()
// 判断触发频率的条件是计算差值后与wait比较,如果小于wait那么就是高频,反之亦然
// wait是执行玩一次后执行下一遍之前所需要等待的时间
// 第一次执行时,previous为0,now为当前时间,所以第一次执行时,立即执行
// 第二次执行时,previous为上一次执行的时间,now为当前时间,所以等待wait毫秒后执行
const interval = wait - (now - previous)
if (interval <= 0) {
fnc.call(that, ...args)
previous = new Date()
clearTimeout(timer)
timer = null
// 这里的清除定时器是为了将已经出发下一次函数后执行的定时器清楚,防止触发多次
} else if(!timer){
// 这里的清除定时器是为了将还没有到达下次执行的时间时,清除定时器,防止执行多次
clearTimeout(timer)
timer = null
timer = setTimeout(function() {
fnc.call(that, ...args)
previous = new Date()
}, interval)
}
}
}
document.onscroll = myThrottle(scroll, 2000)
清除定时器并不是清除调定时器的值,而是清除的状态
总之,防抖和节流技术都可以在不同场景下提高应用性能。防抖适用于多次触发只需执行一次的情况,而节流适用于限制连续触发事件的执行频率。
防抖和节流的实际应用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
没有防抖的输入框
<input type="text" id="unDebounce">
</div>
<div>
有防抖的输入框
<input type="text" id="debounce">
</div>
<div>
有节流的输入框
<input type="text" id="throttle">
</div>
<script>
function ajax(content) {
console.log('ajax request' + content);
}
let inputa = document.getElementById('unDebounce');
inputa.addEventListener('keyup', function (e) {
// console.log(e);
ajax(e.target.value);
})
// 上面是没有加防抖的input框
// function ajax(content){
// console.log('ajax request' + content);
// }
function debounce(func, wait, immediate){
var timer;
return function(...args){
let that = this
// let _args = args
clearTimeout(timer)
timer = setTimeout(function () {
func.call(that, args)
}, wait)
}
}
let inputb = document.getElementById('debounce');
let debounceAjax = debounce(ajax,1000,false)
inputb.addEventListener('keyup',function (e){
// console.log(e);
debounceAjax(e.target.value);
})
// function debounce(fun, delay) {
// return function (args) {
// let that = this
// let _args = args
// clearTimeout(fun.id)
// fun.id = setTimeout(function () {
// fun.call(that, _args)
// }, delay)
// }
// }
// let inputb = document.getElementById('debounce')
// let debounceAjax = debounce(ajax, 500)
// inputb.addEventListener('keyup', function (e) {
// debounceAjax(e.target.value)
// })
function throttle(fun, delay) {
let last, deferTimer
return function (args) {
let that = this
let _args = arguments
let now = +new Date()
if (last && now < last + delay) {
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fun.apply(that, _args)
}, delay)
}else {
last = now
fun.apply(that,_args)
}
}
}
let throttleAjax = throttle(ajax, 1000)
let inputc = document.getElementById('throttle')
inputc.addEventListener('keyup', function(e) {
throttleAjax(e.target.value)
})
</script>
</body>
</html>
闭包的使用场景
js事件监听方法addEventListener()
首先需要了解addEventListener()
的工作原理是将实现EventListener
的函数或对象添加到调用它的EventTarget
上指定事件类型的事件侦听器中。
语法:
element.addEventListener(event, function, useCapture)
参数值:
参数 | 描述 |
---|---|
event | 必须。字符串,指定事件名。 注意: 不要使用 “on” 前缀。 例如,使用 “click” ,而不是使用 “onclick”。 |
function | 必须。指定要事件触发时执行的函数。 当事件对象会作为第一个参数传入函数。 事件对象的类型取决于特定的事件。例如, “click” 事件属于 MouseEvent(鼠标事件) 对象。 |
useCapture | 可选。布尔值,指定事件是否在捕获或冒泡阶段执行。 可能值: – true-事件句柄在捕获阶段执行 –false-false,默认。事件句柄在冒泡阶段执行 |
为什么要使用addEventListener
?
addEventListener
是W3C DOM 规范中提供的注册事件监听器的方法。它的优点包括:
- 它允许给一个事件注册多个监听器。 特别是在使用AJAX库,JavaScript模块,或其他需要第三方库/插件的代码。
- 它提供了一种更精细的手段控制
listener
的触发阶段。(即可以选择捕获或者冒泡)。 - 它对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效。
有关于jsdoc
JSDoc 的目的是记录 JavaScript 应用程序或库的 API。假设您想要记录诸如模块、名称空间、类、方法、方法参数等内容。
JSDoc注释通常应该放在记录代码之前。为了被 JSDoc 解析器识别,每个注释必须以 /**
序列开头。以 /*
、/***
开头或超过3颗星的注释将被忽略。这个特性用于控制解析注释块的功能。
最简单的文档示例就是描述:
/** This is a description of the foo function. */
function foo() {
}
有关于函数闭包
复习不易,先看总结
闭包的作用:
- 延伸了变量的作用范围
- 隐藏变量,避免全局污染
闭包的缺点:
- 因为垃圾回收机制的存在,会导致出现不必要的性能消耗
- 不恰当的使用会出现内存泄漏
什么是闭包?
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。——MDN官网(相关链接)
人话:在一个作用域中可以访问到另一个函数内部的局部变量的函数
怎么判断闭包?
- 存在函数嵌套
- 内部函数访问外部函数作用域的变量
闭包的基本使用:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
其中在displayName的作用域中访问到另一个函数makeFunc的作用域下的局部变量Mozilla
闭包的实现,实际上是利用了JavaScript
中作用域链的概念,简单理解就是:在JavaScript
中,如果在某个作用域下访问某个变量的时候,如果不存在,就一直向外层寻找,直到在全局作用域下找到对应的变量为止,这里就形成了所谓的作用域链。
闭包的特性
可以利用到闭包中被访问作用域中的变量不会被销毁的特性去完成一些应用
- 闭包可以访问到父级函数的变量
- 访问到父级函数的变量不会销毁
var age = 18;
function person(){
age++;
console.log(age);
}
person(); // 19
person(); // 20
person(); // 21
可以看到这里调用了3次函数,age
的值也从18增长到了21,但是这么写会导致全局变量被污染,所以将age
的定义移动到person
函数内部,代码如下:
function person() {
var age = 18;
age++;
console.log(age);
}
person(); // 19
person(); // 19
person(); // 19
但是这又导致了另一个问题,变为局部变量的age
不会自增了,所以那么就可以利用闭包的这个特性将每次调用时的age
保存起来这样就可以实现变量的自增了,代码如下:
function person() {
var age = 18;
return function(){
age++;
console.log(age);
}
}
let getPersonAge = person();
getPersonAge(); // 19
getPersonAge(); // 20
getPersonAge(); // 21
可以这样理解,通过将person
函数赋值给getPersonAge
这个变量,可以看作如下代码
let getPersonAge = function(){
age++;
console.log(age);
}
闭包的应用
循环注册事件
比如就可以利用闭包的特性做循环点击事件,比如下面的给输入框添加onblur
事件:
需求:点击输入框,上面的提示栏显示对应的内容
html
复制代码<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
<script>
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{ 'id': 'email', 'help': 'Your e-mail address' },
{ 'id': 'name', 'help': 'Your full name' },
{ 'id': 'age', 'help': 'Your age (you must be over 16)' }
];
for (var i = 0; i < helpText.length; i++) {
// var func = function (i) {
// document.getElementById(helpText[i].id).onfocus = function () {
// showHelp(helpText[i].help);
// }
// };
// func(i);
(function (i) {
document.getElementById(helpText[i].id).onfocus = function () {
showHelp(helpText[i].help);
}
})(i);
}
}
setupHelp();
</script>
PS:这里如果不想用闭包的话,可以使用ES2015中引入的let
以及const
关键字,或者使用forEach
遍历helpText
时给对应的item
添加focus
事件都可以解决
循环中的定时器
javascript
复制代码var lis = document.querySelector('.test').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
// var fc = function (i) {
// setTimeout(function () {
// console.log(lis[i].innerHTML);
// }, 3000);
// };
// fc(i);
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML);
}, 3000);
})(i);
}
案例1与2的总结:利用立即执行函数所形成的闭包来保存当前循环中的i
的值,进而解决异步任务所带来的i
最后为4(循环结束后i
的值)的问题
模拟私有方法
下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量:
javascript
复制代码Countervar Counter = function(){
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function(){
return changeBy(1);
},
decrement: function(){
return changeBy(-1);
},
getValue: function(){
return privateCounter;
}
}
}
var counterInstance = Counter();
console.log(counterInstance.getValue()); // 0
counterInstance.increment();
counterInstance.increment();
counterInstance.increment();
console.log(counterInstance.getValue()); // 3
counterInstance.decrement();
console.log(counterInstance.getValue()); // 2
还可以将Counter
存在其他变量中以便可以形成多个计数器
javascript
复制代码var counterInstance1 = Counter();
var counterInstance2 = Counter();
// c1 计数器1
console.log(counterInstance1.getValue()); // 0
counterInstance1.increment();
counterInstance1.increment();
counterInstance1.increment();
console.log(counterInstance1.getValue()); // 3
counterInstance1.decrement();
console.log(counterInstance1.getValue()); // 2
// c2 计数器2
console.log(counterInstance2.getValue()); // 0
counterInstance2.increment();
counterInstance2.increment();
console.log(counterInstance2.getValue()); // 2
counterInstance2.decrement();
counterInstance2.decrement();
counterInstance2.decrement();
console.log(counterInstance2.getValue()); // -1