闭包 & 作用域
🧩

闭包 & 作用域

Created
Nov 2, 2022 01:34 AM
Date
Category
Frontend
Tags

作用域

讲到闭包就需要讲到作用域,闭包其实是 js 作用域的一个应用。
 

定义

广义上来说,计算机程序的基本功能就是存储一些值,并且读取、修改这些值。作用域就是定义如何读取、修改值的规则。
狭义上来说,作用域就是变量的集合,决定了变量的可见性。
 

为什么

知道了作用域是定义如何访问变量的规则,那么为什么要有作用域呢?没有不行吗?
还真不行,主要是几个方面:
  1. 防止覆盖变量:在复杂的程序中,变量的同名是不可避免的,变量同名时,应该如何处理呢?就是将他们放在不同的作用域下。
  1. 隐藏变量:定义的某个变量不想被外面的访问,就需要作用域的出现,来规定这个变量能够被哪些位置访问。
  1. 垃圾收集:有了作用域,也就知道了变量被使用的范围,这利于统计变量是否不需要再被使用,方便及时清理销毁。
 

JS 的作用域

那么JS中的作用域规则,是如何定义的呢?
 
JS 的作用域分为3类:
  • 全局作用域:即window 或 global;能够访问所有变量
  • 函数作用域:一个函数内能够访问的变量
  • 块级作用域:一个块内能够访问的变量
作用域可以包含其他作用域,上层作用域不可以访问下层作用域中的变量,下层作用域可以访问上层作用域的变量。JS 通过作用域链来实现作用域。
 
首先,js 中使用 var 声明的变量,均存在于全局作用域中,因为 var a = 1 实质上是 window.a = 1
在 ES6 中,引入了 let、const,他们声明的变量保存在块级作用域中,并且没有变量提升。除了 let、const,其他的语法也会创建块级作用域:
  1. with
  1. try catch

例子

相信很多人遇到过这个case
for (var a = 1; a < 5; a++) { setTimeout(() =>console.log(a)) }
认为会得到 1 - 4 的输出,结果输出的全部都是5。
而解决方案是使用 let
for (let a = 1; a < 5; a++) { setTimeout(() =>console.log(a)) }
为什么会这样?
还记得上面说过的,var 声明的变量是绑定在全局作用域中的,而 let 会创建块级作用域。
var a 的时候,在 window 上创建了a,后续循环中,都在修改 window.a;而最终输出的时候,a 已经变成了 5
在使用 let a 的时候,由于 let 会创建块级作用域,所以每次for 循环执行时,let 会在当前循环的块里面创建一个新的 a,并且这个 a 仅能被这个块访问;所以 a 就是 1 - 4 了。
 

闭包

理解了作用域后,再来看看什么是闭包。
上面提到了,上层作用域不能访问下层作用域的变量,也就是说下面的例子中,a 是访问不到的:
function fun() { const a = 1; // 函数作用域 } console.log(a); // 全局作用域
得到的结果是 undefined
 
这个规则就不能被打破吗?假如我就要访问这个变量,该咋办?
答案是可以打破,只需要创建一个闭包:
function fun() { const a = 1; // 函数作用域1 return function getA() { return a; // 函数作用域2 } } const getA = fun(); // 全局作用域 console.log(getA());
上面的例子中,函数作用域1 包含 函数作用域2,也就是说函数作用域2可以访问到上层作用域 即 函数作用域1 中的变量 a
此时直接将 getA 返回,那么就能在外面获取到 getA 了。
然后再调用 getA,就能拿到变量 a了,就实现了在全局作用域中访问 函数作用域1 中的变量。
 
上面就是创建了一个闭包。总结一下,闭包就是携带作用域的函数。能够打通外部作用域访问内部作用域的通道。
 
最后一句话总结一下闭包与类的区别:
类是有行为的数据(实体),闭包是有数据(作用域)的行为。