从结构化上下文解读闭包

本文详细解析了JavaScript中的词法环境和闭包概念,包括词法环境的规范类型、执行上下文的创建过程以及闭包的形成条件和作用。通过实例说明了catch子句如何创建新的词法环境,并探讨了闭包在保护和保存私有变量方面的功能,强调了其在防止全局变量污染和数据持久化方面的重要性。
摘要由CSDN通过智能技术生成

关于闭包有两种常见的说法,第一种说法是,闭包是由函数以及声明该函数的词法环境组合而成的。这个说法来源于MDN-闭包。

另外一种说法是,闭包是指有权访问另外一个函数作用域中的变量的函数。
闭包确实可以访问另一个函数作用域变量的功能,但闭包不是函数。

我们先来了解词法环境

词法环境

我们可以看看ES5规范第十章(可执行代码和执行上下文)中的第二节词法环境是怎么说的。

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code.

词法环境是一种规范类型(specification type),它定义了标识符和ECMAScript代码中的特定变量及函数之间的联系。
问题来了,规范类型(specification type)又是什么?specification type是Type的一种。从ES5规范中可以看到Type分为language types和specification types两大类。

language types是语言类型,我们熟知的类型,也就是使用ECMAScript的程序员们可以操作的数据类型,包括Undefined, Null, Number, String, Boolean和Object。
而规范类型(specification type)是一种更抽象的元值(meta-values),用于在算法中描述ECMAScript的语言结构和语言类型的具体语义。

我们知道,执行函数会创建新的词法环境。
我们也认同,with语句会“延长”作用域(实际上是调用了NewObjectEnvironment,创建了一个新的词法环境,词法环境的环境记录是一个对象环境记录)。
以上这些是我们比较好理解的。那么catch子句对词法环境做了什么?虽然try-catch平时用得还比较多,但是关于词法环境的细节很多人都不会注意到,包括我!
我们知道,catch子句会有一个错误对象e

function test(value) {
  var a = value;
  try {
    console.log(b);
    // 直接引用一个不存在的变量,会报ReferenceError
  } catch(e) {
    console.log(e, arguments, this)
  }
}
test(1);

在catch子句中打印arguments,只是为了证明catch子句不是一个函数。因为如果catch是一个函数,显然这里打印的arguments就不应该是test函数的arguments。既然catch不是一个函数,那么凭什么可以有一个仅限在catch子句中被访问的错误对象e?
答案就是catch子句使用NewDeclarativeEnvironment创建了一个新的词法环境(catch子句中词法环境的外部词法环境引用指向函数test的词法环境),然后通过CreateMutableBinding和SetMutableBinding将标识符e与新的词法环境的环境记录关联上。

全局环境(The Global Environment)是一个特殊的词法环境,在ECMAScript代码执行之前就被创建。全局环境中的环境记录(Environment Record)是一个对象环境记录(object environment record),它被绑定到一个全局对象(Global Object)上,体现在浏览器环境中,与Global Object关联的就是window对象。
全局环境是一个顶层的词法环境,因此全局环境不再有外部词法环境,或者说它的外部词法环境的引用是null。

执行上下文

当程序控制转移到ECMAScript可执行代码(executable code)时,就进入了一个执行上下文(execution context),执行上下文是一个逻辑上的堆栈结构(Stack)。堆栈中最顶层的执行上下文就是正在运行的执行上下文。
很多人对可执行代码可能又有疑惑了,javascript不都是可执行代码吗?不是的,比如注释(Comment),空白符(White Space)就不是可执行代码。

执行上下文的创建

我们知道,解释执行global code或使用eval function,调用函数都会创建一个新的执行上下文,执行上下文是堆栈结构。
当控制程序进入执行上下文时,会发生下面这3个动作:

this关键字的值被设置。
同时VariableEnvironment(不变的)和initial LexicalEnvironment(可能会变,所以这里说的是initial)被定义。
然后执行声明式绑定初始化操作。

以上这些动作的执行细节取决于代码类型(分为global code, eval code, function code三类)。

闭包

闭包产生的必要条件是:

存在函数嵌套;
嵌套的内部函数必须引用在外部函数中定义的变量;
嵌套的内部函数必须被执行。

闭包的作用

函数执行会形成全新的私有上下文,这个上下文可能被释放,也可能不被释放,不论是否被释放,它的作用是:

保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,而用到的私有变量和其它区域中的变量不会有任何的冲突(防止全局变量污染)
保存:如果上下文不被销毁,那么存储的私有变量的值也不会被销毁,可以被其下级上下文中调取使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值