前端面试之闭包的应用详解(含易错点讲解)

应用一:点击li输出当前li的索引号

 <ul class="nav">
        <li>星辰</li>
        <li>大海</li>
        <li>春暖</li>
        <li>花开</li>
 </ul>

步骤:

》》第1步:获取所有的小li

 // 获取所有小li
    var lis = document.querySelector('.nav').querySelectorAll('li');

》》第2步:循环绑定事件(2种方式)

方式①----利用动态添加属性的方式

 //循环绑定点击事件--利用动态添加属性的方式
    for (var i = 0; i < lis.length; i++) {
        lis[i].index = i;
        lis[i].onclick = function() {
            console.log(this.index);//this指向当前被点击的小li
        }
    }

【解析】

  • 通过循环给每个小li动态添加一个属性index,其值为当前li的索引号
  • 打印输出当前被点击小li的index属性

注意:此处不能直接console.log(i)

 for (var i = 0; i < lis.length; i++) {
        lis[i].onclick = function() {
            console.log(i);
        }
    }

点击每个小li输出结果将为:

 

原因:因为function()是一个异步任务,在点击了小li之后才会执行,而for循环为同步任务会立马执行,故点击任何小li输出结果都为4。这也是为什么需要专门添加一个index属性原因

 

 

方式②-----利用闭包的方式得到当前小li的索引号(通过立即执行函数实现点击小li输出索引号,闭包为立即执行函数)

 

 //循环绑定点击事件--利用立即执行函数(闭包的应用)
    for (var i = 0; i < lis.length; i++) {
        (function(i) {
            lis[i].onclick = function() {
                console.log(i);
            }
        })(i);
    }

【解析】

  • 利用for循环创建了4个立即执行函数
  • 立即执行函数最后一个小括号()相当于是调用该函数,故其可以写入需要被传递给形参的实参。此处实参写入循环变量i的值,function(i)中的形参i用来接受被传递过来的实参i。由于每一次循环都会创建一个立即执行函数,在第一次创建的时候将循环变量i=0传递给实参i,再将实参i=0传递给形参i,当前被创建的立即执行函数中就存储i的值为0,在第二次创建的时候将i=1传递给实参i,再将实参i=1传递给形参i,当前被创建的立即执行函数中就存储当前i的值为1,……循环结束后被依次创建的4个立即执行函数存储的i的值分别为0、1、2、3

  • 需要循环绑定的点击事件函数写入立即执行函数中,当点击了第0个小li时打印出0,当点击了第1个小li时打印出1……,点击事件函数所利用的i值是另外一个函数即立即执行函数里面的i值,因为一个函数使用了另外一个函数里面的变量,故此时就会产生闭包。

【小结】

此处显然方式①比方式②更加简单一点,方式②每次循环都会创建一个立即执行函数,要创建好多好多次反而更麻烦了,所以说并不是所有闭包都是好的,在某些情况下效率反而更低一点。按道理来说,当立即执行函数执行结束后i应该销毁,但立即执行函数存在点击事件需要用到i,故必须等待点击事件执行结束后变量i才可以被销毁,一直不点击那么i一直不能被销毁,这也称为内存泄漏(变量本来需要被销毁但一直未被销毁)。

如果在某些情况下,需要延长变量的作用范围就可使用闭包,在实际开发中需根据实际情况来合理选择是否使用闭包

应用二:3秒之后动态打印所有li元素的内容

 <ul class="nav">
        <li>星辰</li>
        <li>大海</li>
        <li>春暖</li>
        <li>花开</li>
 </ul>

步骤:

》》第1步:获取所有的小li

 // 获取所有小li
    var lis = document.querySelector('.nav').querySelectorAll('li');

》》第2步:循环绑定定时器函数

for (var i = 0; i < lis.length; i++) {
        (function(i) {
            window.setTimeout(function() {
                console.log(lis[i].innerHTML);
            }, 3000)
        })(i);
    }

 

【解析】

利用小闭包立即执行函数来做,将定时器函数写入立即执行函数的里面,每次创建立即执行函数的时将当前的循环变量传入立即执行函数,时间一到定时器函数打印的i值即为其所处立即函数中i的值,一个函数使用了另外一个函数中的变量故 产生了闭包

注意:如果不使用立即执行函数将报错

  for (var i = 0; i < lis.length; i++) {
//去掉立即执行函数 window.setTimeout(
function() { console.log(lis[i].innerHTML); }, 3000) }

3秒后的打印的结果:

 

原因:定时器里面的回调函数属于异步任务,异步任务首先会被放入任务队列中,只有被触发了才会执行,而for循环属于同步任务会立马执行将i的值会变成4,此时时间一到再去调用回调函数打印的将为lis[4],由于不含有lis[4]故将报错。

应用二:计算打车价格

 var car = (function() {
        var start = 13; //起步价
        var total = 0; //总价
        return {
            price: function(n) {}, //返回正常价格 n为公里数
            yd: function(flag) {} //返回拥堵后的费用 flag判断是否拥堵
        }
    })()
//在立即函数中,不需要调用立即执行并return返回了包含两个函数的对象,此处利用car来接收该对象,利用car.price()和car.yd()来调用
 var car = (function() {
        var start = 13; //起步价
        var total = 0; //总价
        return {
            price: function(n) {
                if (n <= 3) {
                    total = start;
                    return total;
                } else {
                    total = start + (n - 3) * 5;
                    return total;
                }
            }, //返回正常价格 n为公里数
            yd: function(flag) {
                    return flag ? total + 10 : total;
                } //返回拥堵后的费用 flag判断是否拥堵
        }
    })();
    console.log(car.price(5)); //23
    console.log(car.yd(true)); //33
    console.log(car.price(5)); //23
    console.log(car.yd(false)); //23
 

start和total在位均为局部变量,被提供给了另外两个函数price和yd使用故产生了闭包。此处立即执行函数为一个闭包,start和total均为闭包变量。

 

posted @ 2021-07-03 17:59  蛋蛋仔  阅读(207)  评论(0编辑  收藏  举报