深入理解JS闭包(一看就会)

前言
JS闭包,对于每一个前端而言都是一个绕不开的概念。本人学习之初,因为闭包这个概念而花费了大量的时间以及精力去理解这个概念。所以在这里,我打算写一篇文章来分享一下本人的学习心得以及我眼中的闭包。
什么是闭包
先来看看百度百科对闭包的定义:

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

上面提到了局部变量,那什么又是局部变量呢? 在理解局部变量之前,我们需要先知道,一个函数的执行流程是什么样的。
执行上下文
执行上下文(execution context)是JavaScript中最重要的一个概念。执行上下文定义了变量或者函数有权访问的其他数据,决定了它们各自的行为。每个执行上下文都有一个与之关联的变量对象。
全局执行上下文是最外围的一个执行上下文。根据ECMAScript所在的宿主环境的不同,该上下文也不同。在浏览器中,全局执行上下文是windows对象,在全局环境下声明的变量为全局变量,所有的全局变量和函数都是作为window对象的属性和方法创建的。某个执行上下文中的所有代码执行完毕后,该上下文被销毁,保存在其中的所有变量和函数也随之销毁(全局执行上下文知道应用程序退出——例如关闭浏览器或者网页时才会被销毁)
而每个函数也都有自己的执行上下文,当执行流进入一个函数时,函数的环境就会被推入一个环境栈当中。而在函数执行完毕后,栈将其环境弹出,把控制权返回给之前的执行环境。

注意:执行上下文,顾名思义,是在某段代码执行时所定义的,所以它是动态的。某个函数在不同的环境调用时,可能会产生不同的执行上下文。

我们来看个例子:

// 全局环境
var a = "全局环境";

function A() {
  var a = "局部环境";
  B();
}

function B() {
  console.log(a);
}
A();

我们来模拟一下浏览器执行上述代码时的流程。

  1. 执行栈中推入全局执行上下文,全局执行上下文中存在全局变量a和函数A与函数B。
    在这里插入图片描述
  2. 执行流进入A函数,在执行栈顶推入函数A的执行上下文,函数A的执行上下文存在局部变量a。
    在这里插入图片描述
  3. 执行函数A代码块中的函数B调用指令,执行栈顶推入函数B的执行上下文,函数B打印变量a。
    在这里插入图片描述
    此时,控制台打印出了全局变量。
    讲到这里,我们不禁发出了疑问,根据执行栈的情况来说,理应是函数B逐层向下查找到函数A的执行上下文,并且打印出局部变量才对呀,为什么反而打印出了全局变量呢?
    这里就引出了作用域这个概念
    作用域
    作用域:指的是一个变量的作用范围。
    在ES6之前,JS的作用域只有全局作用域以及函数作用域两种,在ES6引入了块级作用域(本文暂且先不讨论块级作用域)。
    一个变量如果是在全局环境下定义的,那么这个变量就存在于全局作用域下。以此类推,在函数内部定义的变量,则存在于函数作用域下。

在这里插入图片描述
作用域链
而JS的函数在声明时,会创建一个作用域链。它的用途是保证对执行上下文有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在的上下文的变量对象。如果这个上下文是函数,其变量对象为其内部声明的变量以及入参的arguments对象。作用域链中的下一个变量来自包含(外部)上下文,这样一直延续到全局执行上下文。而JS的函数在声明时,采用的是词法作用域,即在声明时就确定好了作用域链。作用域链的定义是静态的!

在上面的例子里,函数A和函数B在定义时,外部执行上下文只有全局执行上下文,所以其作用域链都为:
函数A/B作用域 -> 全局作用域。

在这里插入图片描述
所以,上文中的例子,函数B内部通过其作用域链先找其内部的变量对象,发现没有变量a,便向上通过作用域链找到了全局作用域下的全局变量,并最终打印出结果。

个人理解:执行上下文所构成的执行栈,归并出了所有变量或函数。但是,并不是在栈顶的执行上下文就一定能向下获取所有的变量或函数,因为作用域的存在,规定了每个作用域内,只能通过作用域链去向下正确的访问各个变量或者函数。执行上下文是动态的,会根据调用函数环境的不同,存在不同的执行栈。而作用域链是静态的,是每个函数在创建之初根据词法作用域就生成的,它规定了在执行栈中,有权访问哪些变量或函数(纯个人总结,如有不对的地方,欢迎在评论区指出)

那么,怎么才能在全局作用域中获取函数作用域中的值呢?
闭包

说了这么多,闭包它终于来了。闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的方式,就是在一个函数内部返回一个匿名函数。我们来改造一下上面的那个例子:

var a = "全局环境";

function A() {
  var a = "局部环境";
  return {
    B: function () {
      console.log(a);
    },
  };
}

var obj = A();
obj.B(); // "局部环境"

现在,我们在函数A的内部返回了一个对象,该对象内部有一个函数B。我们在全局环境下调用了这个函数B,结果打印出了局部环境。这就是一个闭包,我们成功的在全局作用域下调用到了函数A作用域中的变量。究竟是怎么回事呢?让我们再来执行一下这段代码:

  1. 全局执行上下文推入执行栈中,该上下文中存在一个全局变量a,一个函数A和一个对象obj。
    在这里插入图片描述
  2. 调用函数A,执行栈中推入函数A执行上下文。该上下文中存在一个局部变量a。
    在这里插入图片描述
  3. 执行obj.B(),将函数B执行上下文推入执行栈中。
    在这里插入图片描述
  4. 打印变量a,此时根据词法作用域,我们画出作用域链。函数A是在全局环境下声明的,所以其作用域链的下一部分指向了全局作用域,而函数B是在函数A中声明的,所以其作用域链的下一部分指向了函数A的函数作用域。
    在这里插入图片描述

此时,函数B的作用域链指向了函数A的作用域,因此打印出了局部变量。现在,我们通过闭包,可以在全局环境中使用函数作用域下的变量了,是不是感觉很棒。

抽象点来理解闭包:当一个函数内部返回了一个匿名函数,该匿名函数除了自身携带的物品外(内部的变量对象),还背着一个背包(通过作用域链引用的外部函数的变量对象)。在该函数外部就可以通过这个背包来访问这个函数内部的变量了。

注意点:因为闭包返回出来的匿名函数有对其外部函数变量对象的引用,原本外部函数执行完后其变量对象等都会被内存回收,但由于闭包的存在,其外部函数的变量对象还在被引用,所以不会内存回收。我们在使用闭包的同时,还要注意它所带来的副作用。

结语

闭包其实这个概念并不难以理解,只要了解了执行上下文,作用域,作用域链等概念,就能掌握闭包这个概念。而闭包在JS中的使用是非常广泛的,了解了闭包,相信你的JS功底会更加的扎实。由于本人也是在学习阶段,以上的文章如有不对的地方,欢迎在评论区里指出!
在这里插入图片描述
做好这两套题明天直接去阿里巴巴上班
前端实习生
web前端开发工程师(vue)

热门文章

暂无图片
编程学习 ·

C语言二分查找详解

二分查找是一种知名度很高的查找算法,在对有序数列进行查找时效率远高于传统的顺序查找。 下面这张动图对比了二者的效率差距。 二分查找的基本思想就是通过把目标数和当前数列的中间数进行比较,从而确定目标数是在中间数的左边还是右边,将查…
暂无图片
编程学习 ·

GMX 命令分类列表

建模和计算操作命令: 1.1 . 创建拓扑与坐标文件 gmx editconf - 编辑模拟盒子以及写入子组(subgroups) gmx protonate - 结构质子化 gmx x2top - 根据坐标生成原始拓扑文件 gmx solvate - 体系溶剂化 gmx insert-molecules - 将分子插入已有空位 gmx genconf - 增加…
暂无图片
编程学习 ·

一文高效回顾研究生课程《数值分析》重点

数值分析这门课的本质就是用离散的已知点去估计整体,就是由黑盒子产生的结果去估计这个黑盒子。在数学里这个黑盒子就是一个函数嘛,这门课会介绍许多方法去利用离散点最大化地逼近这个函数,甚至它的导数、积分,甚至微分方程的解。…
暂无图片
编程学习 ·

在职阿里5年,一个28岁女软测工程师的心声

简单的先说一下,坐标杭州,14届本科毕业,算上年前在阿里巴巴的面试,一共有面试了有6家公司(因为不想请假,因此只是每个晚上去其他公司面试,所以面试的公司比较少) ​ 编辑切换为居中…
暂无图片
编程学习 ·

字符串左旋c语言

目录 题目: 解题思路: 第一步: 第二步: 第三步: 总代码: 题目: 实现一个函数,可以左旋字符串中的k个字符。 例如: ABCD左旋一个字符得到BCDA ABCD左旋两个字符…
暂无图片
编程学习 ·

设计模式--观察者模式笔记

模式的定义与特点 观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式&#xf…
暂无图片
编程学习 ·

睡觉突然身体动不了,什么是睡眠痽痪症

很多朋友可能有这样的体验,睡觉过程中突然意识清醒,身体却动弹不了。这时候感觉非常恐怖,希望旁边有一个人推自己一下。阳光以前也经常会碰到这样的情况,一年有一百多次,那时候很害怕晚上到来,睡觉了就会出…
暂无图片
编程学习 ·

深入理解C++智能指针——浅析MSVC源码

文章目录unique_ptrshared_ptr 与 weak_ptrstd::bad_weak_ptr 异常std::enable_shared_from_thisunique_ptr unique_ptr 是一个只移型别(move-only type,只移型别还有std::mutex等)。 结合一下工厂模式,看看其基本用法&#xff…
暂无图片
编程学习 ·

@TableField(exist = false)

TableField(exist false) //申明此字段不在数据库存在,但代码中需要用到它,通知Mybatis-plus在做写库操作是忽略它。,.
暂无图片
编程学习 ·

Java Web day15

第十二章文件上传和下载 一、如何实现文件上传 要实现Web开发中的文件上传功能,通常需要完成两步操作:一.是在Web页面中添加上传输入项;二是在Servlet中读取上传文件的数据,并保存到本地硬盘中。 需要使用一个Apache组织提供一个…
暂无图片
编程学习 ·

【51nod 2478】【单调栈】【前缀和】小b接水

小b接水题目解题思路Code51nod 2478 小b接水 题目 输入样例 12 0 1 0 2 1 0 1 3 2 1 2 1输出样例 6解题思路 可以发现最后能拦住水的都是向两边递减高度(?) 不管两个高积木之间的的积木是怎样乱七八糟的高度,最后能用来装水的…
暂无图片
编程学习 ·

花了大半天写了一个UVC扩展单元调试工具

基于DIRECTSHOW 实现的,用的是MFC VS2019. 详见:http://www.usbzh.com/article/detail-761.html 获取方法 加QQ群:952873936,然后在群文件\USB调试工具&测试软件\UVCXU-V1.0(UVC扩展单元调试工具-USB中文网官方版).exe USB中文网 USB中文…
暂无图片
编程学习 ·

贪心(一):区间问题、Huffman树

区间问题 例题一:区间选点 给定 N 个闭区间 [ai,bi]请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。 输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 输入格式 第一行包含整数 N,表示区间数。 接下来 …
暂无图片
编程学习 ·

C语言练习实例——费氏数列

目录 题目 解法 输出结果 题目 Fibonacci为1200年代的欧洲数学家,在他的着作中曾经提到:「若有一只免子每个月生一只小免子,一个月后小免子也开始生产。起初只有一只免子,一个月后就有两只免子,二个月后有三只免子…
暂无图片
编程学习 ·

Android开发(2): Android 资源

个人笔记整理 Android 资源 Android中的资源,一般分为两类: 系统内置资源:Android SDK中所提供的已经定义好的资源,用户可以直接拿来使用。 用户自定义资源:用户自己定义或引入的,只适用于当前应用的资源…
暂无图片
编程学习 ·

零基础如何在短时间内拿到算法offer

​算法工程师是利用算法处理事物的职业 算法(Algorithm)是一系列解决问题的清晰指令,也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。 如果一个算法有缺陷,或不适合于某个问题,执…
暂无图片
编程学习 ·

人工智能:知识图谱实战总结

人工智能python,NLP,知识图谱,机器学习,深度学习人工智能:知识图谱实战前言一、实体建模工具Protegepython,NLP,知识图谱,机器学习,深度学习 人工智能:知识图…
暂无图片
编程学习 ·

【无标题】

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…