闭包详解
前言: 想要了解闭包,首先要了解另一篇作用域的文章。
1.闭包是什么?
js文件执行时会产生一个全局执行上下文,函数执行时会产生一个函数执行上下文。上下文创建时会生成变量对象。
如果在本执行上下文值中访问其他执行下文中的变量对象,或者在其他执行上下文中访问本上下文中的变量,都会产生闭包。
产生闭包的执行上下文不会销毁,不被垃圾回收机制自动回收。一直保留在内存中。
缺点:
闭包导致大量内存不能被js引擎的垃圾回收机制释放。从而造成网页性能问题。
解决办法:
使用块作用域。
2. 闭包应用
1. 监听函数和闭包
function process(data) { // process } var someReallyBigData={/*data*/}; process(someReallyBigData); var btn = document.getElementById('my_button'); btn.addEventListener("click", function click(evt) { console.log('==clicked=='); }, /*capture=*/false); // click函数是监听事件的回调,它拥有涵盖全局作用域的闭包。 // 正常process执行完成后,后面不再使用占用大量内存数据结构someReallyBigData,可以被回收掉。 // 但是因为闭包不可回收,它还会继续保存这个数据结构。
/********************解决方案***************************
function process(data) { // process } { // 因为不在全局作用域中,完事就可以被销毁 var someReallyBigData={/*data*/}; process(someReallyBigData); } var btn = document.getElementById('my_button'); btn.addEventListener("click", function click(evt) { console.log('==clicked=='); }, /*capture=*/false);
2. 块级作用域和闭包
其实在定时器,事件监听,ajax请求等任务中的回调函数,其实都是在使用闭包。
for(var i =1; i<=5; i++) { setTimeout(function timer() { console.log(i); }, 1000 * i); } // 这段代码本来想输入1-5,每秒一次,每次一个值 // 实际情况是每秒输出一个值6,输入了5次。 /**按照词法作用域分析,代码等同于下面*****/ var i = 6; setTimeout(function timer() { console.log(i); }, 1000); setTimeout(function timer() { console.log(i); }, 2000); setTimeout(function timer() { console.log(i); }, 3000); setTimeout(function timer() { console.log(i); }, 4000); setTimeout(function timer() { console.log(i); }, 5000); 因为timer是回调函数,所以,console中的i是最后获取。
如果想要达到最初的目的,则需要每次取值的时候从自己的作用域取值,每次迭代的IIFE函数都有自己的作用域,最后取值的时候从各自的作用域取值。
for(var i = 1; i<=5;i++) { (function IIFE(){ var j = i; setTimeout(function timer() { console.log(j); }, 1000*j); })() } // 等同于 for(var i = 1; i<=5;i++) { (function IIFE(j){ setTimeout(function timer() { console.log(j); }, 1000*j); })(i) }
let实现的闭包块作用域;每次迭代都是一个新的作用域。
for(var i = 1; i<=5;i++) { let j = i; // 闭包的块作用域 setTimeout(function timer() { console.log(j); }, 1000*j); } // 等同于 for(let i = 1; i<=5; i++) { setTimeout(function timer() { console.log(i); }, 1000*i); }
3. 函数柯里化
函数柯里化的实现,本质上应用了闭包的概念。在函数作用域内部声明的函数,在函数作用域外执行
function curry(fn, arr=[]) { const length = fn.length; return function(...args) { arr = arr.concat(args); if(arr.length < length) { return curry(fn, arr); } return fn(...arr); } }