认识闭包,理解闭包,掌握闭包
什么是闭包?
闭包是指函数和其相关的引用环境的组合。简而言之,闭包是由函数和定义该函数时的词法环境组成的。闭包使函数可以访问其外部作用域中的变量,即使在函数被返回或传递到其他地方时仍然有效。有些人可能不能理解,我举下面一个例子:
function foo() {
function bar() {
var age = 18
console.log(myName);
}
var myName = 'Tom'
return bar
}
var myName = 'Jerry'
var fn = foo()
fn() // Tom
- 首先,全局作用域中声明了一个变量
myName
,其值为'Jerry'
。 - 然后,调用
foo()
函数,foo()
函数内部声明了一个局部函数bar()
,以及一个局部变量myName
,其值为'Tom'
。 - 接着,
foo()
函数返回了内部的函数bar
,并将其赋值给全局变量fn
。 - 当执行
fn()
时,实际上是在全局作用域下执行bar()
函数。在bar()
函数中,尝试访问变量myName
,但在bar()
函数内部并未声明myName
,因此会沿着作用域链向上查找。 - 在作用域链中,首先查找到的是
bar()
函数内部的作用域,但是在这个作用域中并没有声明变量myName
。接着向上查找到了foo()
函数的作用域,发现了变量myName
,其值为'Tom'
。 - 因此,
console.log(myName)
会输出Tom
。
如果仔细思考的话,你就会产生疑问,bar函数已经赋值给了全局变量fn,这个时候,fn所在的作用域为全局作用域,为什么不会在全局作用域里找myName,我们不妨画出执行上下文:
当预编译全局后,就要执行,执行到调用foo函数时,再预编译foo,再执行foo,执行完foo后,就要对foo执行上下文进行销毁工作,但是bar函数的词法作用域在foo函数里,可能以后可能会用到foo函数里的东西,你又不得不销毁foo函数,因为这是铁律,用完的东西一定要清除,不这么做,内存都会爆掉,所以就搞出个闭包,当销毁foo执行上下文,就产生了闭包,闭包里存着对bar有用的东西,bar的作用域链就会指向闭包,闭包也会代替foo函数的作用域链指向全局作用域。
看完这个解释后,你就会明白闭包是个什么东西,它有着什么样的作用。
如果把var myName = 'Tom'
删掉,那么也会产生闭包,这是因为当一个函数在其定义的作用域外部被调用时,闭包就产生了,只不过foo函数里没有对bar函数有用的东西,闭包内就为空,作用域链就会顺着闭包指向的一端即全局作用域里找myName,因此最后输出为Jerry。
闭包的原理
当一个函数在其定义的作用域外部被调用时,闭包就产生了。在 JavaScript 中,函数可以访问其定义时的作用域中的变量。当函数被调用时,它可以访问这些变量,即使在其定义的作用域之外。
闭包的应用
- 模块模式: 闭包可以用于模拟私有方法和私有变量。通过将变量和函数封装在闭包中,可以创建一个模块,其中的变量和函数对外部是不可见的。
- 函数工厂: 闭包可以用于创建函数工厂,即生成其他函数的函数。通过返回具有不同初始化参数的函数,可以轻松地创建可定制的函数。
- 事件处理程序: 在事件处理程序中经常使用闭包。当事件发生时,闭包可以访问其外部作用域中的变量和函数,从而实现对事件的响应。
- 异步操作: 闭包在异步编程中也非常有用。它们可以捕获异步操作发生时的上下文,确保回调函数可以访问正确的变量。
闭包的实践技巧
- 避免内存泄漏: 由于闭包会保留对外部作用域的引用,因此在不需要时,要确保取消对闭包的引用,以防止内存泄漏。
- 谨慎使用: 虽然闭包非常有用,但过度使用闭包可能会导致代码难以理解和维护。要谨慎使用闭包,并在必要时进行重构。
- 优化性能: 闭包可能会导致性能问题,特别是在循环中频繁创建闭包时。在性能敏感的代码中,要注意闭包的使用,尽量减少闭包的创建次数。
结论
闭包是 JavaScript 中一个强大且常用的概念,它可以帮助我们编写更加灵活和强大的代码。通过深入理解闭包的原理、应用和实践技巧,我们可以更好地利用闭包,编写出高效、健壮且易于维护的 JavaScript 代码。
转载自:https://juejin.cn/post/7365813532468412468