概念
- 声明在一个函数中的函数,叫做闭包函数。
- 通常情况下,在Javascript语言中,只有函数内部的子函数才能读取局部变量:
function f1(){
var n = 999;
}
console.log(n); // n is not defined
- 闭包是一种特殊的作用域:其返回的内部函数的作用域中保存着父级的变量对象和作用域连接,所以内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被销毁之后。
- 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
特点
- 让外部访问函数内部变量成为可能
- 局部变量会常驻在内存中
- 可以避免使用全局变量,防止全局变量污染
- 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
应用场景实例
防抖debounce
- 设想有此场景:输入框中内容变化需要实时请求接口以获取最新搜索结果,如果在输入完成前输入框内容每变化一下都去请求接口,会造成很多不必要的请求,大大增加服务器压力。
- 解决思路:有变化时延迟一段时间再执行function,若在这段延迟时间内又有新变化,则重新开始延迟
// 定时器期间,有新操作时,清空旧定时器,重设新定时器
var debounce = (fn, wait) => {
let timer, timeStamp=0;
let context, args;
let run = ()=>{
timer= setTimeout(()=>{
fn.apply(context,args);
},wait);
}
let clean = () => {
clearTimeout(timer);
}
return function() {
context = this;
args = arguments;
let now = (new Date()).getTime();
if (now-timeStamp < wait) {
console.log('reset',now);
// 清除定时器,并重新加入延迟
clean();
run();
} else {
console.log('set',now);
run(); // last timer alreay executed, set a new timer
}
timeStamp = now;
}
}
- 代码进一步优化:周期内有新事件触发时,重置定时器开始时间戳,定时器执行时,判断开始时间戳,若开始时间戳被推后,重新设定延时定时器;加入是否立即执行参数。
// 增加前缘触发功能
var debounce = (fn, wait, immediate=false) => {
let timer, startTimeStamp=0;
let context, args;
let run = (timerInterval) => {
timer= setTimeout(() => {
let now = (new Date()).getTime();
let interval = now-startTimeStamp
if(interval < timerInterval) { // the timer start time has been reset,so the interval is less than timerInterval
console.log('debounce reset',timerInterval-interval);
startTimeStamp = now;
run(wait-interval); // reset timer for left time
} else {
if (!immediate) {
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
}
}, timerInterval);
}
return function() {
context = this;
args = arguments;
let now = (new Date()).getTime();
startTimeStamp = now; // set timer start time
if(!timer) {
console.log('debounce set',wait);
if(immediate) {
fn.apply(context,args);
}
run(wait); // last timer alreay executed, set a new timer
}
}
}
节流throttling
- 设想有此场景:有‘搜索’按钮,每点击一次都会重新请求接口,获取并渲染页面表格最新数据,假如短时间内连续点击按钮,依然会造成很多不必要的请求
- 解决思路:在一段时间内只执行最后一次function
// 定时器期间,只执行最后一次操作
var throttling = (fn, wait) => {
let timer;
let context, args;
let run = () => {
timer=setTimeout(()=>{
fn.apply(context,args);
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
console.log("throttle, set");
run();
}else{
console.log("throttle, ignore");
}
}
}
// 增加前缘
var throttling = (fn, wait, immediate) => {
let timer, timeStamp=0;
let context, args;
let run = () => {
timer=setTimeout(()=>{
if(!immediate){
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
console.log("throttle, set");
if(immediate){
fn.apply(context,args);
}
run();
}else{
console.log("throttle, ignore");
}
}
}