javaScript-闭包

2021-07-08·4min
type
Post
summary
status
Published
category
tags
slug
date
Jul 8, 2021
password
icon
📌
闭包指的是,即使外部上下文已经被销毁,但依旧能访问到已销毁上下文中的变量的函数。

什么是闭包?

为了理解闭包,我们首先要回忆一下,执行上下文和执行上下文栈的整体流程
这段代码的上下文变化如下:
  1. 进入全局代码,创建全局执行上下文,将全局执行上下文压入栈中。
  1. 初始化执行全局上下文。创建全局对象,确定作用域链,以及this指向。
  1. 初始化全局上下文的同时,函数checkscope被创建,将父级作用域链保存至内部属性[[scope]]
  1. checkscope被执行,创建checkscope函数执行上下文,压入栈中。
  1. 初始化checkscope函数执行上下文。
    1. 复制[[scope]]属性,创建作用域链。
    2. 用arguments创建活动对象。
    3. 初始化活动对象,即加入形参,var声明的变量,函数声明。
    4. 将活动对象压入作用域顶端,确定完整的作用域链。
    5. 确定this指向。
  1. 初始化checkscope函数的同时,f函数被创建,保存父级作用域链至内部属性[[scope]]。
  1. checkscope函数代码执行完毕,返回f函数,出栈。
  1. foo函数执行(引用同f函数一致)。创建foo函数执行上下文,压入栈中。
  1. foo函数执行上下文初始化。
    1. 复制[[scope]]属性,创建作用域链。
    2. 用arguments创建活动对象。
    3. 初始化活动对象,即加入形参,var声明的变量,函数声明。
    4. 将活动对象压入作用域顶端,确定完整的作用域链。
    5. 确定this指向。
10.foo函数代码执行完毕,返回scope属性,出栈。

分析

这段代码最后输出的结果为local scope。
知道了结果,我们就要思考一个问题,为什么checkscope函数已经被弹出栈,foo函数仍然能访问到它的内部变量scope呢?
关键原因就在于第6步,f函数将父级作用域链保存在了内部属性[[scope]]上。
因此, 即使checkscope函数已经被销毁,f函数仍然能拿到它内部的变量。
这就是所谓闭包的奥秘。

使用闭包的例子

私有变量

由于闭包访问的变量所处的上下文已经被销毁,所以除了闭包本身,其它方式无法访问这个变量。
我们可以利用这一点,创建一个私有变量,只允许通过规定的方式获取或改变这个变量。
1.自增器
创建一个自增器,每调用一次,自增1
2.在实际的业务需求中,有一次遇到需要记录用户浏览时长,并在用户离开页面时上报埋点。这种时候,就可以用上闭包。
创建一个计时器,在用户进入页面时计时,离开页面时获取浏览的时间

缓存

闭包的引用如果不被销毁,那么就会一直留在内存里,我们可以利用这一点,在页面生命周期里实现简单的数据存取。

利用闭包解决全局变量的问题

看下面这道题
可以预知的是,由于for循环中var字声明的是全局变量,所以在最后打印出来的时候,都会是同一个全局变量,都为3;
这时候闭包就可以派上用场了。
每个data[i]都是一个闭包,都引用了已经被销毁的作用域里的变量i,不再引用相同的全局变量的值。
当然这个问题在ES6的let,const出现后已经很好解决,但通过这个问题我们还是能看出闭包的巧妙之处。

闭包的缺点

闭包的缺点显而易见,本该被回收的变量或对象被闭包紧紧抓着,占用了本该被释放的空间。
如果闭包已经不再有用,请马上告诉内存回收器,可以将这个闭包回收了。

参考链接

> cd ..