闭包深入解析
2023-09-15 03:41:10
大家伙应该已经对执行上下文EC再熟悉不过了吧!对于JS的执行流程应该也了然于胸。但是,今天我们不谈EC,我们来聊聊闭包这个东西。
闭包,相信大家都已经听过很多次了吧!但是闭包究竟是怎么形成的,它又是如何在ECS中执行的呢?咱们这就来一探究竟。
闭包的形成
闭包的前提是对作用域和作用域链的理解。所谓的作用域,就是表示一个变量的可用范围。
- 全局作用域
全局作用域就是整个js程序运行的范围。
//定义一个全局变量
let age = 10
function foo(){
console.log(age)
}
// 执行foo,log输出age的值
foo(); // 10
- 函数作用域
在函数执行时,就会形成一个函数作用域,在函数中定义的变量或者常量,都只能在函数内访问。
function foo(){
// 定义一个局部变量
let num = 20
function bar(){
console.log(num)
}
// 执行bar,log输出num的值
bar()
}
// 执行foo
foo() // 20
- 块级作用域
ES6引入了块级作用域,作用域的划分,更加的灵活。
//定义一个全局变量
let age = 10
if(age > 18){
let name = 'kobe'
console.log(name)
}
// 执行if语句块,log输出name的值
if(age > 18){
console.log(name) // 报错
}
- 作用域链
作用域链,就是将当前执行环境的作用域,以及父级执行环境的作用域依次连接起来形成的一条链。当某一个执行环境需要查找一个变量时,会先在自己作用域查找,如果没有,则沿着作用域链向上查找,直到找到为止。
//定义一个全局变量
let age = 10
function foo(){
// 定义一个局部变量
let name = 'kobe'
function bar(){
console.log(age)
console.log(name)
}
// 执行bar,log输出age和name的值
bar()
}
// 执行foo
foo() // 10, kobe
在bar函数执行时,age和name变量都不在bar函数的作用域内,所以需要沿着作用域链向上查找。首先在bar函数的作用域中查找,没有找到,再到foo函数的作用域中查找,依然没有找到。最后在全局作用域中找到了age和name变量。
闭包的形成
所谓的闭包,就是指那些定义在函数内部的函数。闭包的特点是,它可以访问父级函数的作用域,即使父级函数已经执行完毕。
function foo(){
let age = 10
return function(){
console.log(age)
}
}
// 将foo函数返回的函数赋给bar变量
let bar = foo()
// 执行bar函数,log输出age的值
bar() // 10
在这个例子里,bar函数就是闭包,它可以访问foo函数的作用域,并读取age变量的值。即使foo函数已经执行完毕,bar函数仍然可以正常执行。
闭包的执行
闭包的执行过程其实和普通函数的执行过程并没有什么区别,都是在执行环境中进行的。只不过,闭包在执行时,需要先激活父级函数的执行环境,然后才能执行自己。
function foo(){
let age = 10
return function(){
console.log(age)
}
}
// 将foo函数返回的函数赋给bar变量
let bar = foo()
// 执行bar函数,log输出age的值
bar() // 10
当bar函数执行时,首先会激活foo函数的执行环境,然后将age变量的值压入栈中。接着,bar函数开始执行,并将自己的执行环境压入栈中。在bar函数的执行环境中,可以访问到age变量的值。
当bar函数执行完毕后,它的执行环境会被弹出栈中。此时,foo函数的执行环境仍然在栈中,所以age变量的值仍然可以被访问到。
闭包的应用
闭包在实际开发中有很多应用,比如:
- 封装私有变量
闭包可以将变量封装在函数内部,从而使外部无法直接访问这些变量。这样就可以很好地保护变量的隐私性。
- 实现模块化编程
闭包可以实现模块化编程,将不同的功能封装在不同的闭包中,从而提高代码的可维护性和复用性。
- 模拟块级作用域
在ES6之前,js是没有块级作用域的。但是,我们可以使用闭包来模拟块级作用域。
//模拟块级作用域
(function(){
let name = 'kobe'
console.log(name)
})()
console.log(name) // 报错
- 事件处理
闭包在事件处理中也有着广泛的应用。比如,我们可以使用闭包来实现事件代理,从而提高事件处理的效率。
//事件代理
document.addEventListener('click', function(e){
let target = e.target
// 根据target的属性来执行不同的操作
})
- 延迟执行
闭包可以实现延迟执行,比如:
//延迟执行
setTimeout(function(){
// 延迟1秒后执行的代码
}, 1000)
结语
闭包是js中一个非常重要的概念,也是面试中经常考察的知识点。闭包在实际开发中有着广泛的应用,掌握闭包可以大大提高我们的开发效率。
希望大家能够通过这篇文章,对闭包有更深入的了解。