简单而清楚地理解闭包

代码银月使
• 阅读 1174

什么是闭包?
“闭包是指有权访问另一个函数作用域中的变量的函数。”---《JavaScript高级程序设计》
通常来说,当一个函数可以访问另一个函数内部定义的变量(包括属性和方法)时,这个函数可以称之为闭包:

function fnA(){
    var a = "this is fnA.a";
    return function fnB(){
        alert(a);
    }
}

var x = fnA();
x(); // "this is fnA.a"

例子中,我们可以通过x(即fnB)去访问fnA中的内部变量(a),此时我们可以称fnB为闭包。

闭包是如何产生的?
为了更清楚的解释闭包的发生,我们需要先明白“函数的创建”到“函数的调用”到底发生了什么事情。

1、函数被创建时,会创建一条作用域链(下称A链)。然后根据跟创建时的环境,依照“外部函数”、“‘外部函数’的外部函数”、“‘外部函数的外部函数’的外部函数”....“全局函数”顺序,将所有函数的活动对象(可以简单理解为所有的内部变量)添加到这条作用域链上。(大多数非闭包的情况下,函数的外部函数即全局变量)
2、函数被调用时,也会创建一条作用域链(下称B链),并将A链的内容包含到B链中,然后将当前函数的活动对象(可以简单理解为所有的内部变量)添加到B链条的顶端。
3、当访问函数内部变量时,会按照B链中的变量保存的顺序依次访问。即内部变量,(创建时的)外部函数的变量,(创建时的)外部函数的外部函数的变量...全局变量。

下面是一道经典的闭包题:

function fun(n,o) {
    console.log(o)
    return {
    fun:function(m){
        return fun(m,n);
        }
    };
}

var a = fun(0); // undefined。由于会“o”未赋值,所以会显示:undefined。同时返回一个字面量对象,对象内创建一个名为“fun”的函数,并将对象返回赋值给全局变量a。此时a内部的函数fun已经被创建好了,它的作用域链上包含了外部函数(外层的fun函数)的所有变量,其中包含了n(值为0),o(值为undefined);以及全局函数的变量fun(值得注意的是,这个fun属于全局函数的变量)。
a.fun(1); // 0。上面提到。在创建a的内部fun时,它包含的作用域链中包含了n(值为0),o(值为undefined);以及全局函数的变量fun。因此,我们调用(访问)的“fun”是作用域链中给全局函数的函数fun。m=1,n=0,将其赋值给全局函数的函数fun,即:n=(m=)1,o=(n=)0,打印0,值为“0”。
a.fun(2); // 0
a.fun(3); // 0。这里有个“坑”需要注意。在上个步骤“a.fun(1);”中,最后会创建一个对象(fun函数作用域链中的n值为1,o值为0)并返回。但是并没有变量来接收这个对象,更不会影响到a内部作用域链。因此“a.fun(2);”、“a.fun(3);”中,作用域链上的值与“a.fun(1);”中完全一样。


var b = fun(0).fun(1).fun(2).fun(3); // undefined,0,1,2
//这是一条链式调用。为了便于理解,我们将链式调用拆分以下等价的方案:
var b1 = fun(0); // undefined。这个和“ var a = fun(0);”,不重复解释。
var b2 = b1.fun(1); // 0。这里和“a.fun(1);”一样,不重复解释。但是要注意的是,此时有个变量b2接收了b1.fun返回的变量。此时,b2中的函数fun的作用域链的(部分)内容情况:n=1,o=0。
var b3 = b2.fun(2); // 1。“var b2 = b1.fun(1);”中,b2中函数fun的作用域链中的n为1,o为0。调用全局函数的fun时,n=(m=)2,o=(n=)1。因此打印内容为“1”。
var b4 = b3.fun(3); // 2。理由同上。


var c = fun(0).fun(1); // undefined,0
c.fun(2);// 1
c.fun(3);// 1

//为了便于理解,我们将链式调用拆分以下等价的方案进行解释:
var c1 = fun(0); // undefined。这个和“ var a = fun(0);”,不重复解释。
var c = c1.fun(1); // 0。要注意的是,“ c1.fun(1); ”返回的对象由变量c接收,即c中的函数fun作用域链中的变量:n=1,o=0。
c.fun(2);// 1。
c.fun(3);// 1。“ c.fun(2);”中返回的对象不会影响到c。因此此处和执行“c.fun(2);”时一样,c中的函数fun作用域链并未被改变。

我们可以简单理解为:函数创建时,就已经根据上下文环境保存一套所有外部函数(不包含自身内部)的变量。当我们在调用闭包函数时,闭包函数自身不存在的变量,将会在这套变量中查找。

值得一提
1、“变量声明提升”对于闭包的实现是非常重要的。如果变量声明没有被提升,那么我们将无法保存那些在闭包函数创建以后才声明的变量。
2、闭包的机制,作用域链会一直引用自身以外的函数的全部变量,内存回收机制不能及时回收这些变量,从而增大内存开销。

点赞
收藏
评论区
推荐文章
Souleigh ✨ Souleigh ✨
4年前
JS - 从执行上下文的角度来理解闭包
今天看到一篇关于闭包的文章,里面有这样一句话“就我而言对于闭包的理解仅止步于一些概念,看到相关代码知道这是个闭包,但闭包能解决哪些问题场景我了解的并不多”,这说的不就是我么,每每在面试中被问及什么是闭包,大部分情况下得到的答复是(至少我以前是)A函数嵌套B函数,B函数使用了A函数的内部变量,且A函数返回B函数,这就是闭包。而往往面试官想要听到的并不是这样的
Karen110 Karen110
4年前
一篇文章带你了解JavaScript 函数闭包
大家好,我是前端进阶者。JavaScript变量属于本地或者全局范围,使用闭包可以让私有变量成为可能。一、全局变量一个函数可以访问所有定义在函数内部的变量。functionmyFunction()vara4;returnaa;但是函数也可以访问定义在函数之外的变量。vara4;//全局变量funct
菜园前端 菜园前端
2年前
一篇文章教会你什么是闭包
原文链接:什么是闭包?闭包的概念并不复杂,但是它的定义比较绕(就像平时经常用到它,却又说不出来是什么)。可以在一个作用域中调用函数的内部函数并访问到该函数中的作用域的成员,这就是闭包。给一个建议,网上闭包的概念可以搜出来一大堆,但是你真的了解它吗?你有去调
Jacquelyn38 Jacquelyn38
4年前
你不可不知的JS面试题(第三期)
1、什么是闭包?如图所示,闭包就是一个定义在函数内部的函数,其作用是将函数内部和函数外部连接起来。大家知道,作用域的问题,就是在函数内部定义的变量称为局部变量,外部取不到值。下面我们通过代码来更加详细地看一下:function A()       let x  1;       return function B()           c
Bill78 Bill78
5年前
Python 中的闭包
闭包定义:如果在一个内部函数里,对在外部作用于(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包Python中的闭包原文出处:田小计划(http://www.cnblogs.com/wilber2013/p/4658894.html)闭包(closure)是函数式编程的重要的语法结构
御弟哥哥 御弟哥哥
5年前
彻底理解js闭包
在文章开头,我先放出MDN给出的定义:闭包是指那些能够访问独立(自由)变量的函数(变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。现在不需要看懂它,我会在第一个例子中解释清楚它的意思。让我们开始吧!2018.3.20更新:现在MDN上的定义已经改为:"Aclosureisthecomb
Wesley13 Wesley13
4年前
JS 闭包(内存溢出与内存泄漏)(垃圾回收机制)
1.有关闭包定义闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量闭包的特性:函数内再嵌套函数内部函数可以引用外层的参数和变量参数和变量不会被垃圾回收机制回收
Easter79 Easter79
4年前
Swift讲解专题八——闭包
Swift讲解专题八——闭包一、引言      Swift中的闭包是有一定功能的代码块,这十分类似于ObjectiveC中的block语法。Swift中的闭包语法风格十分简洁,其作用和函数的作用相似。二、从一个系统函数看闭包      Swift标准函数库中提供了一个sort排序函数,对
Stella981 Stella981
4年前
JavaScript函数——闭包
闭包概念只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁例子functionouter(){varlocalVal30;returnlocalVal;}
Wesley13 Wesley13
4年前
go 学习笔记之10 分钟简要理解 go 语言闭包技术
闭包是主流编程语言中的一种通用技术,常常和函数式编程进行强强联合,本文主要是介绍Go语言中什么是闭包以及怎么理解闭包.如果读者对于Go语言的闭包还不是特别清楚的话,可以参考上一篇文章go学习笔记之仅仅需要一个示例就能讲清楚什么闭包(https://www.oschina.net/ac
Easter79 Easter79
4年前
Swift3.0 闭包(blcok)的全面介绍及使用
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift中的闭包与C和ObjectiveC中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。Swift会为你管理在捕获过程中涉及到的所有内存操作。闭包表达式语法有如下的一般形式:
代码银月使
代码银月使
Lv1
此地动归念,长年悲倦游。
文章
4
粉丝
0
获赞
0