闭包的理解和应用场景以及遇到的坑

792 阅读3分钟

前言

关于什么是闭包的问题,我好像已经被面试官问到了无数次,几乎是每次必问的样子,可以每次回答都是泛泛而谈。这次写博客就是为了下次让面试官眼前一亮,也是加深自己对闭包的印象,获得新的理解。

闭包含义

能访问父函数的变量并且能立即执行或者被直接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 = {
&emsp;&emsp;name : "My Object",
&emsp;&emsp;getNameFunc : function(){
&emsp;&emsp;&emsp;&emsp;return function(){
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;return this.name;
&emsp;&emsp;&emsp;&emsp;};
&emsp;&emsp;}
};
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);
        };
    }

个人博客地址