原文:从头开始学JavaScript (九)——执行环境和作用域
一、执行环境:定义了变量或者函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有与之关联的变量对象。
变量对象:保存着环境中定义的变量和函数。
作用域链:保证对执行环境有权访问的所有变量和函数的有序访问。
标识符解析:沿着作用域链一级一级地搜索标识符的过程。
通过例子来说明执行环境、变量对象以及作用域链:
1 <script type="text/javascript"> 2 var color = "blue"; 3 function changeColor(){ 4 var anotherColor = "red"; 5 function swapColors(){ 6 var tempColor = anotherColor; 7 anotherColor = color; 8 color = tempColor; 9 //color, anotherColor, tempColor 10 } 11 //color and anotherColor 12 swapColors(); 13 } 14 changeColor(); 15 // color 16 alert("Color is now " + color); 17 </script>
上述例子中的执行环境、执行流程:
画的不好,请各位看官见谅。。。。。。。
作用域链:
执行环境就像一个盒子,全局环境是最外面的盒子,里面包含着很多函数的盒子,每个函数的盒子里面又包含着它自己的子函数盒子,打开的时候是从外而内依次线性打开的,如果只打开了全局环境的盒子,那么只能看到全局环境里那些盒子以外的东西,比如全局盒子有两个子盒子(第一个盒子里面有饼干,第二个盒子里面有蛋糕)和一个小玩具,那么你只能拿到玩具,但是拿不到饼干和蛋糕。如果再继续打开了全局盒子的第一个子盒子,那么既能拿到全局变量里的玩具又能拿到饼干,但是没办法拿到蛋糕。
二、作用域
2.1延长作用域链
虽然执行环境只有两种——全局作用域和函数作用域,但是还是可以通过某种方式来延长作用域链。因为有些语句可以在作用域链的顶部增加一个临时的变量对象。
有两种情况会发生这种现象:
1、try-catch语句的catch块;创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
2、with语句;将指定的对象添加到作用域链中。
1 <script type="text/javascript"> 2 function buildUrl() { 3 var qs = "?debug=true"; 4 with(location){ 5 var url = href + qs; 6 } 7 return url; 8 } 9 var result = buildUrl(); 10 alert(result); 11 </script>
with会把location对象的所有属性和方法包含到变量对象中,并加入到作用域链的顶部。此时访问href实际上就是location.href。
with语句详解:
1 function initUI(){ 2 with(document){ 3 var bd = body, 4 links = getElementsByTagName("a"), 5 i = 0, 6 len = links.length; 7 while(i<len){ 8 update(links[i++]); 9 } 10 getElementById("go-btn").onclick = function(){ 11 start(); 12 }; 13 bd.className = "active" 14 } 15 }
这里使用with语句来避免多次书写document,看上去更高效,实际上产生了性能问题。
当代码流执行到一个with表达式时,执行环境的作用域链会被临时改变,此时with的变量对象会被创建添加到作用域链的前端,这就意味着此时函数的所有局部变量都被推入到第二个作用域链中的变量对象,因此访问代价更高了。
所以,在程序中应避免使用with语句,在这个例子中,只要简单的把document存储在一个局部变量中就可以提升性能。
1 function initUI(){ 2 var doc=document 3 var bd = doc.body, 4 links = doc.getElementsByTagName("a"), 5 i = 0, 6 len = links.length; 7 while(i<len){ 8 update(links[i++]); 9 } 10 doc.getElementById("go-btn").onclick = function(){ 11 start(); 12 }; 13 bd.className = "active" 14 15 }
catch语句详解:
当try代码块中发生错误时,执行过程会跳转到catch语句,然后把异常对象推入一个可变对象并置于作用域的头部。在catch代码块内部,函数的所有局部变量将会被放在第二个作用域链对象中。
1 try{ 2 doSomething(); 3 }catch(ex){ 4 alert(ex.message); 5 //作用域链在此处改变 6 }
请注意,一旦catch语句执行完毕,作用域链机会返回到之前的状态。try-catch语句在代码调试和异常处理中非常有用,因此不建议完全避免。你可以通过优化代码来减少catch语句对性能的影响。一个很好的模式是将错误委托给一个函数处理,例如:
1 try{ 2 doSomething(); 3 }catch(ex){ 4 handleError(ex); //委托给处理器方法 5 }
优化后的代码,handleError方法是catch子句中唯一执行的代码。该函数接收异常对象作为参数,这样你可以更加灵活和统一的处理错误。由于只执行一条语句,且没有局部变量的访问,作用域链的临时改变就不会影响代码性能了。
2.2没有块级作用域
Javscript没有块级作用域。看下面代码:
if(true){ var myvar = "jack"; } alert(myvar);// jack
根据上面我们讨论的,如果有块级作用域,外部是访问不到myvar的。再看下面
for (var i=0;i<5;i++){ console.log(i) } alert(i); // 5
对于有块级作用域的语言来说,i做为for初始化的变量,在for之外是访问不到的,这允分证明了,javascript是没有块级作用域的。