JavaScript中的作用域链原理
执行环境
作用域链的形成与执行环境(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中的作用域链原理的更多相关文章
- 理解JavaScript中的作用域链
理解了作用域链,闭包就不难理解了,所以本文主要谈一谈我对作用域链的理解. 关于JavaScript中变量的作用域,全局变量在程序中始终都有定义.局部变量在声明它的函数体内以及其内部所嵌套的函数内始 ...
- JavaScript中的原型链原理
工作中经常解除到prototype的概念,一开始错误的认为prototype是对象的原型链,其实prototype只能算是JavaScript开放出来的原型链接口,真正的原型链概念应该是__proto ...
- 认识Javascript中的作用域和作用域链
作用域 只要写过java或者c#等语言的同学来说,相信一定能理解作用域的概念,在作用域的范围中,我们可以使用这个作用域的变量,对这个变量进行各种操作.可是,当使用Javascript的时候,相信很多的 ...
- javascript的关键所在---作用域链
javascript的关键所在---作用域链 javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript ...
- 认识javascript中的作用域和上下文
javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的灵活性.每个函数有不同的变量上下文和作用域.这些概念是javascript中一些强大的 ...
- 图解JavaScript中的原型链
转自:http://www.jianshu.com/p/a81692ad5b5d typeof obj 和 obj instanceof Type 在JavaScript中,我们经常用typeof o ...
- 漫谈JavaScript中的作用域(scope)
什么是作用域 程序的执行,离不开作用域,也必须在作用域中才能将代码正确的执行. 所以作用域到底是什么,通俗的说,可以这样理解:作用域就是定义变量的位置,是变量和函数的可访问范围,控制着变量和函数的可见 ...
- 深入理解JavaScript中的作用域和上下文
介绍 JavaScript中有一个被称为作用域(Scope)的特性.虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,我会尽我所能用最简单的方式来解释作用域.理解作用域将使你的代码脱颖而出,减 ...
- JavaScript中的作用域
很多(JavaScript)开发者都在讨论"作用域",但它是什么?它们在JavaScript中的任何地方!我发现很多年轻的开发者不知道作用域是什么.他们中大多数人可以用jQuery ...
随机推荐
- (原)torch中显示nn.Sequential()网络的详细情况
转载请注明出处: http://www.cnblogs.com/darkknightzh/p/6065526.html 本部分多试几次就可以弄得清每一层具体怎么访问了. step1. 网络定义如下: ...
- [JS]九种网页弹窗代码
[1.最基本的弹出窗口代码] 其实代码非常简单: <SCRIPT LANGUAGE="javascript"><!--window.open ("pag ...
- 元组:戴上了枷锁的列表 - 零基础入门学习Python013
元组:戴上了枷锁的列表 让编程改变世界 Change the world by program 元组:戴上了枷锁的列表 由于和列表是近亲关系,所以元组和列表在实际使用上是非常相似的. 我们这节课主要通 ...
- Constructing Roads--hdu1102
Constructing Roads Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...
- 完美解决ListView 与 ScrollView 共存问题
1:首先设置ListView的高度,在setAdapter之后调用此方法. public static void setListViewHeightBasedOnChildren(ListView l ...
- cygwin安装与使用
cygwin安装很简单,下载运行setup.exe程序,一步一步就可以了. 具体安装细节参考:http://www.33lc.com/article/7276.html 安装完成后有如下问题: 在cm ...
- Java vs Python
面试时常问到这两种语言的区别,在此总结一下. Referrence: Udemy:python-vs-java Generally, Python is much simpler to use, an ...
- Direct3D 光照和材质
今天我们来学习下Direct3D里面的光源和材质. 四大光照类型: 环境光 Ambient Light 一个物体没有被光照直接照射,通过每一些物体反射的光线到达这个物体,它也有可能被看到.这种称为 ...
- qt画刷和画笔
# -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #画刷和画笔:QBrush 定义了 QPainter 的填充模式,具 ...
- 使用Open Flash Chart(OFC)制作图表(Struts2处理)
Java开源项目中制作图表比较出色的就是JFreeChart了,相信大家都听说过,它不仅可以做出非常漂亮的柱状图,饼状图,折线图基本图形之外,还能制作甘特图,仪表盘等图表.在Web应用中可以为项目增色 ...