面试中经常被问到闭包的理解,总是回答的不够好,不能完全理解,现在这边做个总结
闭包的基础知识
理解闭包之前必须要理解的变量作用连
- JavaScript变量有两种:全局变量,局部变量
- 局部变量作用域一般在函数里面,在函数之外的视为全局变量
- 一般来说,在函数里面可以访问全局的变量,在函数外面不可以访问函数里面的变量
- Javascript存在“链式作用域”结构(chain scope),这里的链式作用域可以理解为函数嵌套,子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立.
什么是闭包
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式,就是在一个函数内部创建另一个函数。之所以一个内部的函数可以访问其外部的变量,而且在其被返回或是调用时还可以访问,是因为这个内部函数的作用域链中包含外部函数的作用域。
闭包的优点和缺点
闭包对新手是比较难易理解的,不是非必要环境不要使用,使用完了一定要清除闭包
优点
- 实现了可以访问其他作用域变量,并且避免了全局变量对自身词法作用域变量的污染
- 可以把局部变量(自身作用域的变量)驻留在内存中一直保存着上一次执行的值,不会被垃圾回收机制回收,从而避免使用全局变量
缺点
- 局部变量一直驻留在内存中不会被回收,导致内存被爆满,影响程序性能
闭包的常见形式
形式一:
function fun1(){
return function(){
//闭包主体
}
}
var res=fun1();
res();//闭包函数调用
形式二:
(function(i){
//闭包主体
})(i);//闭包函数自调用
闭包的应用
闭包在日常开发中有哪些地方可以使用呢,这里做了几个经典场景
几个 li 中 点击当前 li 返回当前的下标
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
新手如果稍加不注意就会写成
var li = document.querySelectorAll('li');
for(var i=0;i<li.length;i++){
li[i].onclick = function(){
console.log(i)
}
}
这时候点击都返回的是4,因为for循环的i定义的是全局变量,里面的方法的i是向上寻找,就返回全局变量的i,最后点击的时候i已经变成了4
解决办法
for(var i=0;i<li.length;i++){
(function(i){
li[i].onclick = function(){
console.log(i)
}
})(i)
}
在循环的时候把i传到闭包里,变成了一个局部变量,使用闭包来实现
防抖和节流,也可以利用闭包的原理,来解决开发中的一些问题。
防抖
函数防抖(debounce):就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
var debounce= function(fn,interval){
let _fn = fn,
timer;
return function (...args){
if(timer){
clearTimeout(timer);
timer = null;
}
timer = setTimeout(function(){
clearTimeout(timer);
timer = null;
_fn.bind(this)(...args)
},interval||500)
}
}
函数节流(throttle):所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。
var throttle = function(fn,interval){
let _fn = fn,
timer,
onefn =true; //是否是第一次 如果不需要可以去掉
return function (...args){
if(onefn){
//第一次直接执行
_fn.bind(this)(...args)
return onefn=false;
}
if(timer){
return false;
}
timer = setTimeout(function(){
clearTimeout(timer);
timer = null;
_fn.bind(this)(...args)
},interval||500)
}
}