返回

闭包深入解析

前端

大家伙应该已经对执行上下文EC再熟悉不过了吧!对于JS的执行流程应该也了然于胸。但是,今天我们不谈EC,我们来聊聊闭包这个东西。

闭包,相信大家都已经听过很多次了吧!但是闭包究竟是怎么形成的,它又是如何在ECS中执行的呢?咱们这就来一探究竟。

闭包的形成

闭包的前提是对作用域和作用域链的理解。所谓的作用域,就是表示一个变量的可用范围。

  1. 全局作用域

全局作用域就是整个js程序运行的范围。

//定义一个全局变量
let age = 10
function foo(){
    console.log(age)
}
// 执行foo,log输出age的值
foo(); // 10
  1. 函数作用域

在函数执行时,就会形成一个函数作用域,在函数中定义的变量或者常量,都只能在函数内访问。

function foo(){
    // 定义一个局部变量
    let num = 20
    function bar(){
        console.log(num)
    }
    // 执行bar,log输出num的值
    bar()
}
// 执行foo
foo() // 20
  1. 块级作用域

ES6引入了块级作用域,作用域的划分,更加的灵活。

//定义一个全局变量
let age = 10
if(age > 18){
    let name = 'kobe'
    console.log(name)
}
// 执行if语句块,log输出name的值
if(age > 18){
    console.log(name) // 报错
}
  1. 作用域链

作用域链,就是将当前执行环境的作用域,以及父级执行环境的作用域依次连接起来形成的一条链。当某一个执行环境需要查找一个变量时,会先在自己作用域查找,如果没有,则沿着作用域链向上查找,直到找到为止。

//定义一个全局变量
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变量的值仍然可以被访问到。

闭包的应用

闭包在实际开发中有很多应用,比如:

  1. 封装私有变量

闭包可以将变量封装在函数内部,从而使外部无法直接访问这些变量。这样就可以很好地保护变量的隐私性。

  1. 实现模块化编程

闭包可以实现模块化编程,将不同的功能封装在不同的闭包中,从而提高代码的可维护性和复用性。

  1. 模拟块级作用域

在ES6之前,js是没有块级作用域的。但是,我们可以使用闭包来模拟块级作用域。

//模拟块级作用域
(function(){
    let name = 'kobe'
    console.log(name)
})()
console.log(name) // 报错
  1. 事件处理

闭包在事件处理中也有着广泛的应用。比如,我们可以使用闭包来实现事件代理,从而提高事件处理的效率。

//事件代理
document.addEventListener('click', function(e){
    let target = e.target
    // 根据target的属性来执行不同的操作
})
  1. 延迟执行

闭包可以实现延迟执行,比如:

//延迟执行
setTimeout(function(){
    // 延迟1秒后执行的代码
}, 1000)

结语

闭包是js中一个非常重要的概念,也是面试中经常考察的知识点。闭包在实际开发中有着广泛的应用,掌握闭包可以大大提高我们的开发效率。

希望大家能够通过这篇文章,对闭包有更深入的了解。