【javaScript】闭包

闭包

相关文章

闭包


一、什么是闭包

闭包是指函数和声明该函数的词法环境的组合。这意味着闭包可以访问函数内部的变量,即使函数已经执行完毕并且离开了作用域,闭包仍然可以访问这些变量。

二、闭包的主要用途

  • 保护变量: 闭包可以使得函数内部的变量被隐藏和保护起来,不会被外部访问和修改。这种封装性有助于提高代码的安全性和可维护性。

  • 模块化开发: 闭包可以用来创建模块化的代码结构,通过将变量和函数封装在闭包内部,可以避免全局命名空间的污染,同时也可以减少代码之间的耦合度,提高代码的可重用性。

  • 延长变量的生命周期: 闭包可以延长函数内部变量的生命周期,使得函数执行完毕后,仍然可以访问到这些变量。这在一些需要长时间存储状态或者需要缓存数据的场景中非常有用。

  • 实现私有变量和方法: 闭包可以模拟出私有变量和方法的效果,通过将变量和方法封装在闭包内部,外部无法直接访问,从而实现了信息隐藏和封装性。

  • 解决异步编程问题: 闭包可以用于解决 JavaScript 中的异步编程问题,例如使用闭包可以在回调函数中访问到外部作用域的变量,从而解决了回调函数中的变量作用域问题。

  • 实现函数柯里化(Currying): 闭包可以用于实现函数柯里化,即将多个参数的函数转化为只接受单个参数的函数序列,从而方便函数的组合和复用。

三、使用闭包注意事项

  • 内存泄漏: 闭包可以使得函数内部的变量在函数执行完毕后仍然被引用,导致内存泄漏问题。因此,需要注意在不需要使用闭包的时候及时释放闭包中的变量引用,以避免内存泄漏。

  • 变量泄露: 在闭包内部访问外部作用域中的变量时,需要注意变量的生命周期,以避免在闭包外部修改了闭包内部的变量,或者在闭包外部访问了闭包内部的变量,导致意外的行为发生。

  • 性能问题: 闭包会带来额外的内存开销和执行开销,因为闭包会保留对外部作用域中变量的引用。在处理大量数据或者频繁调用闭包的情况下,可能会导致性能问题。因此,需要谨慎使用闭包,避免滥用。

  • 循环中的闭包: 在循环中创建闭包时,需要注意闭包内部对循环变量的引用。由于 JavaScript 中的作用域链特性,闭包内部的循环变量会被所有闭包共享,可能会导致意外的结果。为了避免这种情况,可以使用立即执行函数或者闭包工厂函数来创建一个新的作用域,以隔离每个闭包的作用域。

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

    在这个例子中,我们期望在每隔 1 秒钟输出一个数字,依次为 0、1、2、3、4。然而,实际上会输出五个数字 5。这是因为在循环中创建的 setTimeout 中的回调函数形成了闭包,它们共享了循环变量 i 的引用。当 setTimeout 回调函数被执行时,循环已经结束,i 的值变成了 5,因此会输出五次 5。

    为了解决这个问题,可以使用立即执行函数来创建一个新的作用域,以隔离每个闭包的作用域:

    1
    2
    3
    4
    5
    6
    7
    for (var i = 0; i < 5; i++) {
    (function(i) {
    setTimeout(function() {
    console.log(i);
    }, 1000);
    })(i);
    }

    在这个例子中,立即执行函数 (function(i) { … })(i) 创建了一个新的作用域,并将循环变量 i 传递给闭包,保持了闭包内部对循环变量的引用。这样就可以正确地输出 0、1、2、3、4,而不会输出意外的结果。

  • 代码可读性和维护性: 使用闭包可以实现许多功能,但是过度使用闭包可能会导致代码变得复杂和难以理解。因此,需要在代码可读性和维护性之间进行权衡,选择合适的闭包使用方式。

四、闭包的使用场景

1、封装私有变量和方法

Counter 函数返回一个包含了 increment、decrement 和 getCount 方法的对象。这些方法都可以访问并操作 count 变量,但是外部无法直接访问 count 变量,实现了信息隐藏和封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Counter() {
let count = 0;

return {
increment: function() {
count++;
},
decrement: function() {
count--;
},
getCount: function() {
return count;
}
};
}

const counter = Counter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 输出 2

2、实现函数柯里化

add 函数接受一个参数 a,返回一个函数,这个返回的函数接受另一个参数 b,并返回 a + b 的结果。通过这种方式,可以实现对一个函数的参数进行分步传递,方便函数的复用和组合。

1
2
3
4
5
6
7
8
function add(a) {
return function(b) {
return a + b;
};
}

const addFive = add(5);
console.log(addFive(3)); // 输出 8

3、处理异步编程问题

fetchData 函数接受一个 URL 参数,返回一个函数,这个返回的函数接受一个回调函数作为参数。内部通过 fetch API 发起异步请求,并在请求完成后调用回调函数,将获取到的数据作为参数传递给回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function fetchData(url) {
return function(callback) {
fetch(url)
.then(response => response.json())
.then(data => callback(data))
.catch(error => console.error('Error fetching data:', error));
};
}

const fetchUserData = fetchData('https://api.example.com/users');
fetchUserData(function(userData) {
console.log(userData);
});

4、缓存函数结果

使用闭包可以缓存函数的计算结果,提高函数的执行效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function memoize(func) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (!cache[key]) {
cache[key] = func(...args);
}
return cache[key];
};
}

const memoizedAdd = memoize(function(a, b) {
console.log('Calculating...');
return a + b;
});

喜欢这篇文章?打赏一下支持一下作者吧!
【javaScript】闭包
https://www.cccccl.com/20210504/javascript/闭包/
作者
Jeffrey
发布于
2021年5月4日
许可协议