I. 介绍
对闭包的定义和概述
闭包是指在函数内部定义函数,并且可以访问到外部函数的变量的一种机制。
通俗来说,闭包就是“函数内部的函数”,且这个内部函数可以访问到外部函数的变量,即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的作用域。
这种特性使闭包在JavaScript中非常有用,可以用于模块化代码、实现私有变量、保存函数状态
等。理解和掌握闭包的机制有助于提高代码的质量和性能。但是,过度使用闭包也会导致一些问题,如内存泄漏和性能下降。因此,在使用闭包时需谨慎权衡利弊。
为什么理解闭包很重要
理解闭包很重要的原因有以下几点:
- JavaScript中的作用域和闭包是理解函数式编程和模块化编程的重要基础。如果没有理解闭包,那么很难掌握这些编程范式。
- JavaScript中的变量作用域和变量生命周期比较复杂,闭包可以用来解决一些变量作用域的问题,例如实现私有变量和避免全局变量的污染等。
- 闭包可以用于保持函数状态,并且可以根据需要动态地创建并存储函数状态。因此,理解闭包可以让我们写出更丰富、灵活和可维护的 JavaScript 代码。
- JavaScript 中闭包机制的正确性和利用是一个考验编程能力的问题,理解并正确使用闭包可以提高代码质量、代码可扩展性和代码安全性等方面的问题。
- 闭包在 JavaScript 中的应用非常广泛,包括
jQuery、React、Angular
等热门框架中都有闭包的应用。掌握闭包不仅可以编写更好的代码,还可以方便我们理解和调试现有的项目。
II. 函数与作用域
函数的作用域和生命周期
在JavaScript
中,每个函数都有自己的作用域,即变量和函数在函数内部的活动范围。函数的作用域由函数声明所在的位置和函数参数的作用域组成
。当函数返回后,它的作用域会被销毁,其中声明的变量和函数也会被销毁,这个过程叫做函数的生命周期。
在函数外部声明的变量叫做全局变量,它在整个程序都可见,并且不会在任何函数生命周期结束后被销毁,直到程序运行结束或者手动删除,才会被销毁。
在函数内部声明的变量叫做局部变量,它只在函数内部的作用域范围内可见。当函数执行完毕并返回后,局部变量也会被销毁,释放其所占用的内存空间。
在函数中,如果某个变量被内部函数引用,即使这个内部函数已经在该函数执行后返回到外部,该变量仍然会存在于内部函数的作用域中,这就是闭包的一个特性。由于闭包会让变量继续存在于内存中,因此需要注意闭包的使用,避免出现内存泄漏等问题。
闭包是如何利用函数的作用域的
闭包利用了JavaScript中的函数作用域以及函数作用域链的特性。当内部函数引用外部函数的变量时,由于函数作用域链的存在,内部函数可以访问外部函数的变量。在外部函数返回后,如果内部函数仍然保持对外部函数变量的引用,那么这个内部函数就形成了闭包,可以访问外部函数变量,即使外部函数已经执行完毕并且变量已经离开作用域。
在JavaScript中,所有的函数都是闭包,因为它们都可以访问所在作用域的变量。但是在一般情况下,我们所称的“闭包”通常是指具有特定功能的内部函数,它可以访问外层函数的变量,即使外层函数已经执行完毕。
通过使用闭包,我们可以方便地创建私有变量和创建对象和构造函数等功能。由于闭包可以访问到外部函数的变量,所以可以通过闭包来创建私有变量和方法,以保护变量不被外部访问。同时,由于闭包可以保存对外部变量的引用,因此可以用来实现一些具有状态的函数,使得函数调用状态可以持久化,而不用依赖外部全局变量的存储。
III. 闭包的实现
闭包的实现方式
闭包的实现方式通常有两种。
第一种方式是使用函数表达式,将函数直接赋值给变量或者作为函数参数传递。在函数内部,再定义一个内部函数来使用外部函数的变量。
第二种方式是使用函数声明方式,在函数内部声明一个内部函数,并将其作为返回值返回出去,这个内部函数可以访问外部函数的变量,从而形成闭包。
例如,以下是使用函数表达式实现闭包的示例:
function createCounter() { let count = 0; return function() { count++; console.log(count); }; } const counterA = createCounter(); counterA(); // 输出1 counterA(); // 输出2 counterA(); // 输出3
在这个示例中,createCounter
函数返回内部的匿名函数,该匿名函数可以访问外部 createCounter
函数的变量 count
。 counterA = createCounter()
把匿名函数赋值给变量 counterA,当 counterA 再次调用时,变量 count
仍然存在,并得到后续累加处理。从而实现了一个计数器。
另外一个使用函数声明方式实现闭包的示例如下:
function add(x) { return function(y) { return x + y; } } const addFive = add(5); console.log(addFive(2)); // 输出 7
在这个示例中,add
函数返回一个匿名函数,该匿名函数可以访问外部函数 add
的参数 x
, addFive = add(5)
把匿名函数赋值给变量 addFive
,接下来再通过 addFive
的调用来获得 y
的值,再加上 x
的值,从而实现一个加法函数。
如何创建闭包
在JavaScript中创建闭包需要满足以下两个条件:
- 外部函数返回内部函数。
- 内部函数可以访问外部函数的变量。
实现闭包有多种方式,下面列举几个常见的方式:
1. 函数表达式
可以通过函数表达式的方式,将内部函数作为外部函数的一个属性或者直接赋值给另外一个变量,来实现闭包。
function outer() { let a = 10; return function() { console.log(a); } } const inner = outer(); inner(); // 输出 10
在这个例子中,内部函数访问外部函数的变量 a
,形成了闭包,在 inner()
被调用的时候,依然可以访问 a
的值。
2. 立即执行函数(IIFE)
立即执行函数是指一种立即执行的 JavaScript 函数。常见的写法是将函数定义包裹在 (function() {})()
或者 (function() {}())
中。这种方式也可以用来创建闭包。
const inner = (function() { let a = 10; return function() { console.log(a); } })(); inner(); // 输出 10
在这个例子中,内部函数即立即执行函数的返回值,形成了闭包。
3. 对象方法
通过将内部函数作为对象的方法也可以形成闭包。
const object = { a: 10, inner: function() { console.log(this.a); } }; object.inner(); // 输出 10
在这个例子中,内部函数是一个对象方法,因此可以访问对象属性 a
的值,形成闭包。
这些仅是常见的几种方式,实际上可以灵活运用JavaScript语言特性,来实现闭包。
闭包的应用场景
闭包在JavaScript中的应用非常广泛,以下是几个常见的应用场景:
1. 私有变量和方法
由于JavaScript语言并没有提供私有变量和方法的原生支持,因此可以利用闭包来实现私有变量和方法。外部函数的变量和方法只能在内部函数中访问,而外部作用域无法访问。这种方式可以有效地保护变量,防止它们被外部访问。
function createPerson(name) { let age = 0; return { setName: function(newName) { name = newName; }, setAge: function(newAge) { age = newAge; }, getAge: function() { return age; } } } const person = createPerson('小明'); console.log(person.getAge()); // 输出 0 person.setAge(18); console.log(person.getAge()); // 输出 18
在这个例子中,外部函数 createPerson
返回一个对象,该对象包含三个方法,其中 setAge
方法可以访问外部函数 createPerson
的变量 age
,而变量 name
则只能在外部函数 createPerson
内部使用。
闭包治愈“全局变量恐惧症”,利用闭包实现JavaScript私有变量(二)https://developer.aliyun.com/article/1426536