执行环境

 作用域链的形成与执行环境(Execution Environment)相关,在JavaScript当中,产生执行环境有如下3中情形:

1 进入全局环境

2 调用eval函数

3 调用function

  在一个执行环境A上可以创建执行环境B,执行环境B又可以创建执行环境C...,这一系列的执行环境构成执行环境栈,最新创建的执行环境位于栈顶(栈底永远是全局执行环境),当栈顶执行环境结束之后(与之相关的代码执行结束)就会被弹出站外,底下的执行环境就会成为新的栈顶。如下图所示:

  一个执行环境由3部分组成:LexicalEnvironment,VariableEnvironment,ThisBinding。其中的ThisBinding就是this值,而LexicalEnvironment和VariableEnvironent相当于两个指针,它们指向Lexical Environment(注意不是LexicalEnvironment),Lexical Environment里面包含的就是标识符。

  Lexical Environment又由2部分组成:Environment Record和一个outer指针,其中outer指针指向当前执行环境下面执行环境的Lexical Environment,而Environment Record里面就是绑定的程序中各种标示符。

  Environment Record又可以细分为2种类型:一种叫Declaration Environment Record,另一种是Object Environment Record。这两种类型的Environment Record都记录程序中使用到的标识符,不同之处在于,Declaration Environment Record主要绑定的是变量的声明和函数的声明,而Object Environment Record会关联一个Object,Object Environment Record里面绑定的都是和这个变量相关的属性(比如with语法),这样在JavaScript代码里面访问这个变量的属性时就可以不用显示指定该Object。

  整个关系如下图所示:

  需要注意的是,执行环境栈最底层的全局执行环境的outer指针指向null。当查找某一个标示符时,就从最顶端执行环境的Lexical Environment开始查找,如果找不到,就进入到outer指针指向的下一个Lexical Environment里面进行查找(上面的图示是为了演示,实际中outer指针指向的Lexical Environment有可能不是相邻执行环境的Lexical Environment),直到查找到该变量或者outer指针指向的是null,因此,由outer指针连接的Lexical Environment就是当前函数运行时的作用域链。

  在执行环境刚建立的过程中,LexicalEnvironment和VariableEnvironment指向的同一个Lexical Environment,但是如果在该执行环境下遇到了特殊的语法,比如with,那么LexicalEnvironment就会指向新的Lexical Record,当with运行结束,LexicalEnvironment又会指向VariableEnvironment所指的Lexical Environment,如下面代码所示:

function f() {
var i = 1; with(document) {
write("Hello");
} var j = 2;
}

在执行with语句之前的执行环境如下图所示:

执行with语句时执行环境如下图所示(注意没有创建新的执行环境,只是增加了一个Lexical Environment):

执行with语句后执行环境如下图所示:

Environment Record的构成

  不管Environment Record是Declaration Environment Record,还是Object Environment Record,都绑定的是当前执行环境下的标示符的信息,即Key-Value,Key就是标示符名,Value就是对应的值,如下图所示:

进入函数形成执行环境

假设有如下代码:

function f(a, b, c) {
var i = 1;
var j = 2; //函数声明(function declaration)
function inner(k, l, m) {
var i = 1;
var p = 2;
var q = 3;
} //函数表达式(function expression)
var fVar = function(x, y, z) {
var i = 1;
var r = 2;
var s = 3;
}
}

当在JavaScript当中以f(1, 2, 3)调用这个函数时,就会为这个函数创建新执行环境,在执行该函数内部任何代码之前,会先将函数里面的标示符信息放到Environment Record当中。Environment Record当中的绑定标示符可以分成4部分(这4个部分的顺序在Environment Record当中固定,并且每一部分内部的顺序依据标识符出现的先后):函数参数,函数声明,arguments,变量声明,经过绑定后,函数f的执行环境如下:

  参数标示符直接绑定的是传给函数的实参值;

  函数声明标示符绑定的是函数对象(这也是函数声明标示符可以先使用,后声明原因,因为在代码运行之前,函数声明标示符就已经加入到了Environment Record中,并且绑定了函数对象);

  arguments绑定的是arguments对象;

  而变量声明(包括函数表达式,也可以看成是变量声明),绑定的都是undefined,只有当代码真正运行到赋值给变量的语句,变量才会持有会变成我们赋予变量的值(这也是在变量声明之前可以使用该变量,但是变量值总是undefined的原因)。

  上图中Environment Record里面的变量i和j是函数f里面的变量,与函数声明inner以及函数表达式fVar里面的变量无关,因为此时只是对它们进行了定义,还没有进行调用。

需要注意的是:

1) Environment Record会绑定arguments标示符的条件是参数名和函数声明的标识符没有命名为arguments的;

2) 如果多个函数使用同一个标识符,Environment Record也只会有一条记录,并且该记录绑定的是最后声明的函数对象,即后声明的函数对象覆盖之前的函数对象,并且如果函数声明标识符合参数标识符重名,那么函数标识符覆盖参数标识符,即如果有如下代码:

function f(a, b, c) {
alert(a);
function a() {
...
}
alert(a);
}

当使用f(1, 2, 3)调用时,两处alert都会显示function a,而不是参数a。

3)如果变量声明标识符与之前参数,函数声明,arguments标识符重名,那么变量标识符不会被绑定,即如果有下面代码:

function f(a, b, c) {
function i() {
...
} alert(i);
var i = 1;
alert(i);
}

当使用f(1, 2, 3)调用时,第一处的alert会显示function i,而不是undefined值,因为变量声明i与函数i重名,不会被绑定;第二个alert会显示1,因为运行了var i =1;之后,Environment Record里面标识符i对应的值被赋予了1。

同时,如果多次声明同一个变量,Environment Record里面也只会有一条记录。

  剩下的一个问题是,在调用函数的时候,新的执行环境是如何与外层的执行环境联系到一起的,即新执行环境的outer指针是如何知道该指向哪一个执行环境的。答案就是,在定义函数时,函数对象的内部属性[[scope]]会引用定义自己时所在的Lexical Environment,当发生调用时,outer指针就指向[[scope]]所引用的Lexical Environment。假设有如下代码:

function f1() {
var f = f2();
f();
} function f2() {
var i = 1;
return function() {
i = 2;
}
}

当调用f2时,执行环境如下图所示:

当调用f2结束时,执行环境如下图所示:

f2调用结束,f2相关的执行环境从栈顶弹出,而f2执行环境引用的Lexical Environment由于有函数对象f的[[scope]]属性引用,因此不会随着f2的执行环境弹出栈顶而被垃圾回收。

当调用函数f时,执行环境如下图所示:

从图上可以看到,从最顶端f的Lexical Environment,沿着outer指针,函数f可以访问到函数f2里面的局部变量i,这就是闭包的原理。

参考资料

ECMA-262

JavaScript中的作用域链原理的更多相关文章

  1. 理解JavaScript中的作用域链

    理解了作用域链,闭包就不难理解了,所以本文主要谈一谈我对作用域链的理解.   关于JavaScript中变量的作用域,全局变量在程序中始终都有定义.局部变量在声明它的函数体内以及其内部所嵌套的函数内始 ...

  2. JavaScript中的原型链原理

    工作中经常解除到prototype的概念,一开始错误的认为prototype是对象的原型链,其实prototype只能算是JavaScript开放出来的原型链接口,真正的原型链概念应该是__proto ...

  3. 认识Javascript中的作用域和作用域链

    作用域 只要写过java或者c#等语言的同学来说,相信一定能理解作用域的概念,在作用域的范围中,我们可以使用这个作用域的变量,对这个变量进行各种操作.可是,当使用Javascript的时候,相信很多的 ...

  4. javascript的关键所在---作用域链

    javascript的关键所在---作用域链 javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript ...

  5. 认识javascript中的作用域和上下文

    javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的灵活性.每个函数有不同的变量上下文和作用域.这些概念是javascript中一些强大的 ...

  6. 图解JavaScript中的原型链

    转自:http://www.jianshu.com/p/a81692ad5b5d typeof obj 和 obj instanceof Type 在JavaScript中,我们经常用typeof o ...

  7. 漫谈JavaScript中的作用域(scope)

    什么是作用域 程序的执行,离不开作用域,也必须在作用域中才能将代码正确的执行. 所以作用域到底是什么,通俗的说,可以这样理解:作用域就是定义变量的位置,是变量和函数的可访问范围,控制着变量和函数的可见 ...

  8. 深入理解JavaScript中的作用域和上下文

    介绍 JavaScript中有一个被称为作用域(Scope)的特性.虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,我会尽我所能用最简单的方式来解释作用域.理解作用域将使你的代码脱颖而出,减 ...

  9. JavaScript中的作用域

    很多(JavaScript)开发者都在讨论"作用域",但它是什么?它们在JavaScript中的任何地方!我发现很多年轻的开发者不知道作用域是什么.他们中大多数人可以用jQuery ...

随机推荐

  1. C#将十六进制的文本转换到整型数据

    1 length1 = Int32.Parse(szLine.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);//计算这一行 ...

  2. “Win”组合键

    Windows组合键功能: 单独按下显示或隐藏 [开始] 功能表. +Break 显示 [系统内容] 对话方块. +D 显示桌面. +M 最小化所有的视窗. +Shift+M 还原最小化的视窗. +E ...

  3. liunx下安装mysql没有初始密码的解决方法

    #/etc/init.d/mysql stop #cd /usr/local/mysql #mysqld_safe --user=mysql --skip-grant-tables --skip-ne ...

  4. fiddler2使用文档

    http://www.trinea.cn/android/android-network-sniffer/

  5. unity3D对象的显示和隐藏

    SetActive/active/SetActiveRecursively 后两者比较旧,现在通常用第一个SetActive 必须先new一个gameobject对象用于实例化,然后再设置其activ ...

  6. 实例解析shell子进程(subshell )

    http://blog.csdn.net/sosodream/article/details/5683515 http://blog.csdn.net/firefoxbug/article/detai ...

  7. 省去在线安装 直接下载Chrome官方离线安装包

    首页>软件之家>便捷上网 省去在线安装 直接下载Chrome官方离线安装包 2013-10-12 23:22:02来源:IT之家 原创作者:阿象责编:阿象人气:54487 评论:19 谷歌 ...

  8. QT中QProcess调用命令行的痛苦经历(调用Winrar,设置工作目录,获得输出,注意引号与括号,等等)

    QT中QProcess调用命令行的痛苦经历   阅读目录 创建压缩包的方法 在QT中调用命令行 在QT中调用C++创建的dll 在QT程序中需要将某些目录和文件压缩为一个rar的压缩包,于是想到了在Q ...

  9. poj 3684 Physics Experiment(数学,物理)

    Description Simon ), the first ball is released and falls down due to the gravity. After that, the b ...

  10. qt简单界面更新代码(菜鸟级)(部分代码)

    qt简单界面更新代码(菜鸟级)(部分代码)self.timers_1=QtCore.QTimer(self)self.timers_1.timeout.connect(self.min_1)self. ...