前言
关于什么是闭包的问题,我好像已经被面试官问到了无数次,几乎是每次必问的样子,可以每次回答都是泛泛而谈。这次写博客就是为了下次让面试官眼前一亮,也是加深自己对闭包的印象,获得新的理解。
闭包含义
能访问父函数的变量并且能立即执行或者被直接return出来的函数称为 闭包。
来点通俗的:
函数 A 内部return一个函数 B,函数 B 可以访问到函数 A 中的a变量,那么函数 B 就是闭包。
解释: 因为JS语言是没有块级作用域的,只有函数作用域。当函数内部访问变量时,会先在自己的内部作用域里去寻找,直到没有,然后就会不停向更外层的作用域去寻找,找到则停止,没找到则继续找,直到全局作用域里。所以函数B就能访问到函数A的a变量,而这个a变量就是被A函数保护起来的变量。如果不进行释放,它是会一直存在的。
应用场景
1. 递归时使用闭包
用递归的方法求阶乘
function factorial (num) {
if (num<=1) {
return 1;
} // 被保护的表达式
return num * factorial(num-1);
}
factorial(3); // 3 * 2 * 1 = 6
使用闭包遇到的坑
坑1: 内存泄漏
比如要为某个元素添加onclick事件的时候。
function showId () {
var btn = document.getElementById('btn');
btn.onclick = function () {
alert(btn.id);
}
}
这样写el.id会造成函数运行完后,无法JS的垃圾回收机制无法回收el,造成内存泄漏。为了避免它,最好改成这样写:
function showId () {
var btn = document.getElementById('btn');
var id = btn.id;
btn.onclick = function () {
alert(id); // 这样写会导致id去访问外部函数作用域的id,造成内存泄漏
}
ben = null;
}
坑2: this指向问题
var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
console.log(object.getNameFunc()()); //The Window 没有想到吧
这个时候不要看第二个括号,object.getNameFunc()这是一个全局函数,然后再加一个括号,就是调用全局函数了,谁调用,this指向就指向谁。
坑3:引用的变量可能发生变化
就是那个for循环点击按钮的那个栗子。
<body>
<input type="button" value="按钮1"/>
<input type="button" value="按钮2"/>
<input type="button" value="按钮3"/>
<input type="button" value="按钮4"/>
<input type="button" value="按钮5"/>
<input type="button" value="按钮6"/>
<input type="button" value="按钮7"/>
<input type="button" value="按钮8"/>
<input type="button" value="按钮9"/>
<input type="button" value="按钮10"/>
</body>
<script>
var inputList=document.querySelectorAll("input");
for(var i=0; i<inputList.length; i++){
inputList[i].οnclick=function () {
alert(i); // 弹出的数字都是 10
};
}
</script>
解决办法有第三种:
方法一:闭包,将i作为函数参数值传递给内部函数。
var inputList=document.querySelectorAll("input");
for(var i=0; i<inputList.length; i++){
// 采用立即执行函数创建作用域
(function(j){
inputList[j].οnclick=function () {
alert(j);
};
}(i));
}
方法二:闭包,将i作为函数参数值传递给内部函数。
// 也是立即执行函数
var inputList=document.querySelectorAll("input");
for(var i=0; i<inputList.length; i++){
inputList[i].οnclick=(function (j) {
return function(){
alert(j);
};
}(i));
}
方法三:ES6里,用let定义变量,会产生块级作用域(最方便吧这种)
var inputList=document.querySelectorAll("input");
for(let i=0; i<inputList.length; i++){
inputList[i].οnclick=function () {
alert(i);
};
}