1.作用域(scope):在javascript没有块级作用域,是由函数来划分的。变量和函数的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域(with和eval除外)。当定义了一个函数,当前的作用域链就保存起来,并且成为函数的内部状态的一部份。在最顶级作用域链仅由全局对象组成,而不和词法作用域相关,然而,当定义一个嵌套的函数时,作用域链就包括外面的包含函数。这意味着嵌套函数可以访问包含函数的所有参数和局部变量。尽管当一个函数定义时作用域链就固定了,但作用域链中定义的属性还没有固定。作用域链是活的,并且函数被调用时,可以访问任何当前的绑定。
2.作用域链(scope chain):存储变量对象的集合(环境栈?),保证对执行环境有权访问的所有变量和函数的有序访问,也就是用于标识符解析(变量访问)。
3.执行环境(execution context):定义了变量和函数有权访问的其他数据,决定了它们的各自行为。每个执行环境都有一个与之关联的变量对象。
在web浏览器中,window对象就是全局执行环境。每个函数都有自己的执行环境,当函数执行完毕,该环境被销毁,保存在其中的变量和函数也随之销毁。
4.变量对象(variable object):保存了环境中定义的所有变量和函数。该对象无法访问,仅供解析器在后台使用。
5.活动对象(activation object):如果执行环境是函数,则将其活动对象作为变量对象(调用对象?)。活动对象最开始的两个属性是arguments和this
一、作用域链
a) 一个函数创建时,javascript后台(引擎)会默认创建一个仅供后台使用的内部属性[[Scope]],此属性存储函数的作用域链,如果是全局函数,此时则包含一个变量对象(全局变量),如果是嵌套函数(闭包),作用域链还加上了父函数的变量对象。例如下面的这个全局函数:
function add(num1,num2){ var sum = num1 + num2; return sum; }
(此图是函数定义时的作用域链)
b) 函数被调用时--add(5,10),javascript后台会创建一个内部对象(execution context)--“执行环境”或“运行期上下文”,执行环境有它自己的作用域链,执行环境创建时就以定义函数时的作用域链初始化它自己的作用域链,并且随后创建了一个活动对象,活动对象作为函数执行期的一个变量对象,包含所有局部变量(在函数内定义的)、命名参数、arguments、this,它会被推入到执行环境作用域链的前端(如下图)。每执行一次函数都会创建一个新的执行环境,当函数执行完毕执行环境就会被销毁。
(此图是函数运行时的作用域链)
另外关于延长作用域链问题:以下的两种情况会使作用域链延长
1) try-catch语句的catch块;
2) with语句;
这个两个语句都会再原本的作用域链的前端添加一个变量对象。对于with语句来说,新添加的变量对象包含着with括号中指定对象的所有属性和方法所作的变量声明。对于catch来说,当try块发生错误时,代码执行流程自动转入到catch块,并将异常对象推入到作用域链的前端。catch块执行完毕后,作用域链就会返回原来的状态。
请看下面的例子:
function initUI(){ with(document){ var bd = body, links = getElementsByTagName("a"), i = 0, len = links.length; while(i<len){ update(links[i++]); } getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active" } }
当代码流执行到一个with表达式时,执行环境的作用域链会被临时改变,此时with的变量对象会被创建添加到作用域链的前端,这就意味着此时函数的所有局部变量都被推入到第二个作用域链中的变量对象,如下图:
由上图可清晰的看到,在执行with语句时,访问局部变量的代价更高了。所以尽可能避免使用with语句,可以使用局部变量代替
var doc = document; // 代替with(document){...}