转载

深入执行环境、作用域链和闭包

执行环境对象和作用域链

执行环境,又称 执行上下文 ,是指一个 函数在执行 的时候所能直接引用的变量等的一个集合。

在JavaScript引擎中,执行环境是由一类特殊的对象—— 执行环境对象 ——来实现的。由于一个函数执行的时候可能对应不同的上下文,所以每次函数执行的时候都会由引擎为该函数创建一个独一无二的执行环境对象。函数执行完毕时,由垃圾回收(GC)机制来决定是否将该执行环境对象回收。

为了区别执行环境和执行上下文,我将下文中的执行环境称作“ 执行上下文 ”。

注意:全局环境(全局作用域所在的环境)虽然不是一个函数,但是其中的代码执行时,也会有一个相应的执行环境对象与之对应。

<!-- more -->

不同执行环境中的变量是存在依赖关系的。

例如:一个全局执行环境下创建的函数在执行时,其执行环境需要知道全局执行环境中的变量:window、document、以及其他声明的全局变量(注意:未声明的变量作为window对象的属性,和声明过的全局变量有略微的不同之处)。熟悉 函数作用域 概念的同学,不难理解这种依赖关系。

这种依赖关系是通过执行环境对象中的一个特殊的属性,引用 创建该函数时的所对应的执行环境对象 来实现的。由于这种引用关系可以形成一条引用链,一个函数执行时,引擎对变量的解析就是通过对执行环境对象引用链的遍历来解析确定的。

这个引用链有个高大上的、经常听到的名字—— 作用域链

为了解释作用域链的机制,我们再来引入一个 scope 属性的概念。

函数对象的scope属性

我们知道,JavaScript的函数是Function构造函数的实例,本质是一类特殊的对象。某一个对象只可能在某一个独一无二的执行上下文中创建,但是函数对象会在不同的执行上下文中执行。

函数对象有很多属性,其中一个就是只有在JavaScript引擎中可见的 scope 属性。这个 scope 属性指向 创建该函数时对应的执行环境对象

该函数执行的时候,使用函数内的函数作用域变量创建一个新执行环境对象,并且引用 scope 属性指向的执行环境对象。这个执行环境对象和该函数的执行上下文相对应。

这三者对应关系如下图所示:

深入执行环境、作用域链和闭包

但是scope属性依然引用创建这个函数的执行环境对象,原因跟上面的解释是一样的:

一个函数只能在某个特定的执行上下文中创建,但是会在不同的执行上下文中执行。

函数执行时变量解析

从作用域链的角度解释:首先从该函数所对应的执行环境对象中搜索该变量,如果没有则沿着作用域链继续搜索,直到找到为止。然后将数据取出或者存储。

这里有一个优化问题:不要在代码中过多的引用作用域链中离头结点(即当前执行环境对象)较远的结点中的变量。解决办法:将这个非头结点中的变量赋值给函数局部变量变为头结点中的变量,这样就不需要每次都去搜索作用域链了。

题外话:this

this 可以认为是一个特殊的变量,代表函数的调用者。每一个执行环境对象中都有一个 this ,但是变量搜索时,只需搜索当前的执行环境对象就可以找到这个变量。当需要找到作用域链中非头结点的 this 时,需要将其保存为其他特定的能被引用到的局部变量来处理。

闭包

现在我们可以看看小宇宙中的黑魔法了。

函数B在函数A中被返回。那么创建函数B的执行环境对象就是函数A对应的执行环境对象。那么函数B的 scope 对象会保存函数A的执行环境对象。

而函数A的执行环境对象作用域链保存了函数A在执行时能解析到的变量。所以函数B中就能通过其 scope 属性访问函数A的执行环境中的变量。

假设函数B的调用者无法访问函数A中的变量,那么它只能通过函数B的行为来获得函数A中的变量状态。

此时函数B由于其 scope 属性保存了函数A的执行环境对象的作用域链,从而形成一个 闭包

结束

一点微小的见解。

本文涉及JavaScript界的敏感话题,故而,如有纰漏,欢迎吐槽。

原文  https://segmentfault.com/a/1190000006145364
正文到此结束
Loading...