资讯专栏INFORMATION COLUMN

简单而清楚地理解闭包

SimonMa / 2906人阅读

摘要:大多数非闭包的情况下,函数的外部函数即全局变量函数被调用时,也会创建一条作用域链下称链,并将链的内容包含到链中,然后将当前函数的活动对象可以简单理解为所有的内部变量添加到链条的顶端。

什么是闭包?
“闭包是指有权访问另一个函数作用域中的变量的函数。”---《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、闭包的机制,作用域链会一直引用自身以外的函数的全部变量,内存回收机制不能及时回收这些变量,从而增大内存开销。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101038.html

相关文章

  • js闭包的底层实现和开发技巧

    摘要:没有清空的原因是,内部函数返回的匿名函数的作用域链仍然保有对外部函数的变量的引用。在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,直至作为作用域链终点的全局执行环境。 前言 闭包这个概念几乎成了JavaScript面试者必问的话题之一,可以毫不客气地说对闭包的理解和运用体现了一名js工程师的功底。那么闭包到底是什么,它又能带来什么特别的作用?网上...

    Caizhenhao 评论0 收藏0
  • 前端基础

    摘要:谈起闭包,它可是两个核心技术之一异步基于打造前端持续集成开发环境本文将以一个标准的项目为例,完全抛弃传统的前端项目开发部署方式,基于容器技术打造一个精简的前端持续集成的开发环境。 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。 不论你是javascript新手还是老鸟,不论是面试求职,还是日...

    graf 评论0 收藏0
  • 理解 JavaScript this

    摘要:回调函数在回调函数中的指向也会发生变化。在闭包回调函数赋值等场景下我们都可以利用来改变的指向,以达到我们的预期。文章参考系列文章理解闭包理解执行栈理解作用域理解数据类型与变量原文发布在我的公众号,点击查看。 这是本系列的第 5 篇文章。 还记得上一篇文章中的闭包吗?点击查看文章 理解 JavaScript 闭包 。 在聊 this 之前,先来复习一下闭包: var name = Nei...

    zombieda 评论0 收藏0
  • JavaScript闭包,只学这篇就够了

    摘要:当在中调用匿名函数时,它们用的都是同一个闭包,而且在这个闭包中使用了和的当前值的值为因为循环已经结束,的值为。最好将闭包当作是一个函数的入口创建的,而局部变量是被添加进这个闭包的。 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义。 其实只要理解了核心概念,闭包并不是那么的难于理解。但是,网上充斥了太多学术性的文章,对于...

    CoderBear 评论0 收藏0
  • 十分钟快速了解《你不知道的 JavaScript》(上卷)

    摘要:最近刚刚看完了你不知道的上卷,对有了更进一步的了解。你不知道的上卷由两部分组成,第一部分是作用域和闭包,第二部分是和对象原型。附录词法这一章并没有说明机制,只是介绍了中的箭头函数引入的行为词法。第章混合对象类类理论类的机制类的继承混入。 最近刚刚看完了《你不知道的 JavaScript》上卷,对 JavaScript 有了更进一步的了解。 《你不知道的 JavaScript》上卷由两部...

    赵春朋 评论0 收藏0

发表评论

0条评论

SimonMa

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<