高阶函数:闭包的应用

时间函数 setTimeout

let a = 0;
setTimeout(function () {
    console.log(++a);
}, 1000);

这个例子用到了时间函数setTimeout,在等待 1 秒钟后对变量 a 进行加 1 的操作。这是一个闭包,因为 setTimeout 中的匿名函数对外部变量进行访问,然后该函数又被 setTimeout 方法引用。满足了形成闭包的两个条件。所以即使外部上下文结束了,1 秒后仍然能对变量 a 进行加 1 操作。

在 DOM 的事件操作中

<body>
	<input id="count" type="button" value="计数">
	<script>
	    (function(){
	        var count = document.getElementById("count"); 
	        var num = 0;
	   		count.onclick = function(){
	            console.log(++num);
	        }
	    })()
	</script>
</body>

在这个例子中,onclick 指向的函数中访问了外部变量 num,同时该函数又被 onclick 事件引用了,满足 2 个条件,是闭包。所以当外部上下文结束后,你继续点击按钮,在触发的事件处理方法中仍然能访问到变量 num。

img 数据上报

const report = function (src) {
    let img = new Image();
    img.src = src;
}
report('http://192.168.*.**/****/*****');

在这个例子中,我们可以通过查询后台的记录得知,因为一些低版本的浏览器的实现存在 bug,在这些浏览器下使用 report 函数进行数据上报时会丢失 30% 左右的数据,也就是说,report 函数并不是每一次都成功发起了 HTTP 请求。丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数在调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求就会丢失掉。
而这个例子我们可以用闭包重构一下

const report = (function () {
    let imgs = [];
    return function (src) {
        let img = new Image();
        imgs.push(img);
        img.src = src;
    }
})();

这样就可以保证每次请求的成功。

闭包引起的奇怪问题

for (var i = 1; i <= 3; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

我们预期的结果是过 1 秒后分别输出 i 变量的值为 1,2,3。但是,执行的结果是:4,4,4。

既然是闭包引起的问题,那么解决的方法就是去掉闭包。我们知道形成闭包有两个条件,只要不满足其一,那就不再是闭包。

  • 条件一,内部函数被外部引用,这个我们没办法去掉。
  • 条件二,内部函数访问外部变量。这个条件我们有办法去掉,比如:
for (var i = 1; i <= 3; i++) {
    (function (index) {
        setTimeout(function () {
            console.log(index);
        }, 1000);
    })(i)
}

用一个立即执行函数重新包裹一下,这样 setTimeout 中就可以不用访问 for 循环声明的变量 i 了。而是采用调用函数传参的方式把变量 i 的值传给了 setTimeout,这样它们就不再形成闭包。也就是说 setTimeout 中访问的已经不是外部的变量 i,所以即使 i 的值增长到 4,跟它内部也没关系,最后达到了我们想要的效果。

当然这个来说也是相对比较麻烦的,因此这里提供一种更加简便的方法:

for (let i = 1; i <= 3; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

使用 ES 6 中的 let 关键字。它声明的变量有块作用域,如果将它放在循环中,那么每次循环都会有一个新的变量 i,这样即使有闭包也没问题,因为每个闭包保存的都是不同的 i 变量。

闭包与面向对象

过程与数据的结合是形容面向对象中的"对象"时经常使用的表达。对象以属性的形式包含了数据,以方法的形式包含了过程。而闭包则是在过程中以环境的形式包含了数据。通常用面向对象思想能实现的功能,用闭包也能够实现,反之亦然。
下面我们使用闭包来实现一个完整的面向对象的系统。

const Test = function(){
    let value = 0;
    return {
        call : function(){
            value++;
            console.log(value);
        }
    }
}
const test = new Test();
test.call(); // 1
test.call(); // 2
test.call(); // 3

如果换成面向对象的写法:

const test = {
    value: 0,
    call: function () {
        this.value++;
        console.log(this.value);
    }
}
test.call(); // 1
test.call(); // 2
test.call(); // 3

或者

const Test = function () {
    this.value = 0;
}
Test.prototype.call = function () {
    this.value++;
    console.log(this.value);
}
const test = new Test();
test.call(); // 1
test.call(); // 2
test.call(); // 3

本文章取自本人JS语言导师谢老师的学习总结,同时也感谢谢老师对我的谆谆教诲,感谢他带我走上前端这条道路,并让我为之不断向前

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
闭包在编程中有很多应用场景,以下是一些常见的例子: 1. 数据封装和私有变量:闭包可以用来创建私有变量,从而实现数据封装和保护。这在模块化编程和面向对象编程中非常有用。 2. 函数工厂:闭包可以用来创建一系列相关的函数,这些函数共享相同的外部变量。这在创建类似于Python中的装饰器或JavaScript中的高阶函数时非常有用。 3. 延迟执行和计时器:闭包可以用来实现延迟执行和定时器功能。例如,在JavaScript中,setTimeout和setInterval函数使用闭包来实现延迟执行和定时器功能。 4. 记忆化(Memoization):闭包可以用来实现记忆化,即缓存函数的计算结果,以便在后续调用中重用。这可以提高函数的性能,特别是在处理计算密集型任务时。 5. 事件处理和回调函数:在JavaScript等事件驱动的编程环境中,闭包常用于实现事件处理和回调函数闭包可以捕获事件处理函数的上下文,使得事件处理函数可以访问其所需的外部变量。 6. 部分应用(Partial Application)和柯里化(Currying):闭包可以用来实现部分应用和柯里化,这是一种将多参数函数转换为一系列单参数函数的技术。这可以简化函数调用,使得代码更加简洁和可读。 7. 实现迭代器和生成器:在某些编程语言中,例如Python和JavaScript,闭包可以用来实现迭代器和生成器,这是一种用于遍历数据结构的高效方法。
07-14

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值