执行环境、作用域

http://www.cnblogs.com/aaronjs/articles/2167431.html

在javascript的学习中,执行环境、作用域是2个非常非常重要和基本的概念,理解了这2个概念对于javsacript中很多脚本的运行结果就能明白其中的道理了,比如搞清作用域和执行环境对于闭包的理解至关重要。
一、执行环境(exection context,也有称之为执行上下文)
所有 JavaScript 代码都是在一个执行环境中被执行的。执行环境是一个概念,一种机制,用来完成JavaScript运行时在作用域、生存期等方面的处理,它定义了变量或函数是否有权访问其他数据,决定各自行为。
在javascript中,可执行的JavaScript代码分三种类型:
1. Global Code,即全局的、不在任何函数里面的代码,例如:一个js文件、嵌入在HTML页面中的js代码等。
2. Eval Code,即使用eval()函数动态执行的JS代码。
3. Function Code,即用户自定义函数中的函数体JS代码。
不同类型的JavaScript代码具有不同的执行环境,这里我们不考虑evel code,对应于global code和function code存在2种执行环境:全局执行环境和函数执行环境。

  在一个页面中,第一次载入JS代码时创建一个全局执行环境,全局执行环境是最外围的执行环境,在Web浏览器中,全局执行环境被认为是window对象。因此,所有的全局变量和函数都是作为window对象的属性和方法创建的。
   当调用一个 JavaScript 函数时,该函数就会进入与该函数相对应的执行环境。如果又调用了另外一个函数(或者递归地调用同一个函数),则又会创建一个新的执行环境,并且在函数调用期间执行过程都处于该环境中。当调用的函数返回后,执行过程会返回原始执行环境。因而,运行中的 JavaScript 代码就构成了一个执行环境栈。

Code1:

function Fn1(){

     function Fn2(){
        alert(document.body.tagName);//BODY
        //other code...
     }
   Fn2();
}
Fn1();
//code here
          (图1 执行环境栈  摘自:[笨蛋的座右铭](http://www.cnblogs.com/fool/archive/2010/10/16/1853326.html)的博文)

   程序在进入每个执行环境的时候,JavaScript引擎在内部创建一个对象,叫做变量对象(Variable Object)。对应函数的每一个参数,在Variable Object上添加一个属性,属性的名字、值与参数的名字、值相同。函数中每声明一个变量,也会在Variable Object上添加一个属性,名字就是变量名,因此为变量赋值就是给Variable Object对应的属性赋值。在函数中访问参数或者局部变量时,就是在variable Object上搜索相应的属性,返回其值。(另外注意:一般情况下Variable Object是一个内部对象,JS代码中无法直接访问。规范中对其实现方式也不做要求,因此它可能只是引擎内部的一种数据结构。)
   大致处理方式就这样,但作用域的概念不只这么简单,例如函数体中可以使用全局变量、函数嵌套定义时情况更复杂点。这些情况下怎样处理?JavaScript引擎将不同执行位置上的Variable Object按照规则构建一个链表,在访问一个变量时,先在链表的第1个Variable Object上查找,如果没有找到则继续在第2个Variable Object上查找,直到搜索结束。这就是Scope/Scope Chain的大致概念。

二、Scope/Scope Chain(作用域/作用域链)
当代码在一个环境中执行时,都会创建基于Variable Object的一个作用域链。 作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。整个作用域链是由不同执行位置上的Variable Object按照规则所构建一个链表。作用域链的最前端,始终是当前正在执行的代码所在环境的Variable Object。如果这个环境是函数(比如Fn2),则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,就是函数内部的arguments对象。作用域链中的下一个Variable Object来自该函数(Fn2)的包含环境(也就是Fn1),而再下一个Variable object来自再下一个包含环境。这样,一直延续到全局执行环境,全局执行环境的Variable Object始终是作用域链中的最后一个对象。
如上所述,作用域链感觉就是一个Variable Object链表,当访问一个变量时,先在链表的第一个Variable Object(最前端)上查找,如果没有找到则继续在第二个Variable Object上查找,直到搜索结束,也就是搜索到全局执行环境的Variable Object中。这也就形成了Scope Chain的概念。
与上面Code1代码对应的作用域链图如下所示(摘自:笨蛋的座右铭的博文,这个图感觉不是很理想,不如下面的图3更形象,把右侧这部分调个个就好了。)

   (图2 作用域链图)
 如上图所示,对于Code1代码中的函数Fn2所对应的作用域链为:Fn2 Variable Object->Fn1 variable Object->全局对象。也就是说,在Fn2函数中进行变量访问时,首先会在Fn2 Variable object访问内进行寻找,如果没有找到,则向上,搜索Fn1 Variable Object中是否存在,直至找到,停止搜索。
   再拿《javascript高级程序设计第二版》中提到的例子来说明一下。代码如下所示:

Code2:

var color="blue";
function changecolor(){
    var anothercolor="red";
    function swapcolors(){
        var tempcolor=anothercolor;
        anothercolor=color;
        color=tempcolor;
       // Todo something             
     }
    swapcolors();
}
changecolor();
//这里不能访问tempcolor和anocolor;但是可以访问color;
alert("Color is now  "+color);
  在Code2代码中,涉及3个执行环境全局环境、changecolor函数的局部环境和swapcolor局部环境。该段代码的作用域链如下图所示。

(图3摘自:《javascript高级程序设计第二版》)

上图中,
-》全局环境有1个变量color和1个函数changecolor()。
-》changecolor()函数的局部环境中具有1个anothercolor属性和1个swapcolors函数,当然,changecolor函数中可以访问自身以及它外围(也就是全局环境)中的变量。
-》swapcolor()函数的局部环境中具有1个变量tempcolor。在该函数内部可以访问上面的2个环境(changecolor和window)中的所有变量,因为那2个环境都是它的父执行环境。

 通过上面的分析,我们可以得知内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间是线性、有次序的。每个环境都可以向上搜索作用域链,以便查询变量和函数名;但任何环境不能通过向下搜索作用域链条而进入另一个执行环境。对于上述例子的swapcolor()函数而言,其作用域链包括:swapcolor()的变量对象、changecolor()变量对象和全局对象。swapcolor()的局部环境开始先在自己的Variable Object中搜索变量和函数名,找不到,则向上搜索changecolor作用域链。。。。。以此类推。但是,changecolor()函数是无法访问swapcolor中的变量。

关于作用域总结以下几条:
1、javascript 没有块级作用域。
直接上代码:

for(var i=0;i<10;i++){
   doSomething(i);
}
alert(i); // output :10,why?
 上述代码运行后会返回10,为什么呢?如果是同样的java或是c#代码,则不会是10,可能会提示运行错误,因为i只存在于for循环体重,在运行完for循环后,for中的所有变量就被销毁了。而在javascript中则不是这样的,在for中的变量声明将会添加到当前的执行环境中(这里是全局执行环境),因此在for循环完后,变量i依旧存在于循环外部的执行环境。因此,会输出10。

2、声明变量
使用var声明变量时,这个变量将被自动添加到距离最近的可用环境中。对于函数而言,自然声明的变量就会被添加到函数的局部环境中,变量在整个函数环境内都是可用的。
但是,如果变量没有是用var进行声明,将会被添加到全局环境,也就是说成位全局变量了。
所以在函数体内,进行声明时,一般要在开头用var进行声明。

最后出几个小例题:

var x = 1;
function rain(){
    alert( x );    //弹出 'undefined',而不是1,也不是10,why?
     var x = '10';
    alert( x );    //弹出 '10',why?
}
rain()

为什么会是代码中所说明的结果呢?
我认为和2个事情有关:作用域和预解析。我们可以很容易得出上述代码的作用域链。
window全局环境和rain()函数局部环境。window全局环境中存在全局变量x和rain,而rain()函数的局部环境中包括:局部变量x。因此,在执行rain()函数时,不可能去访问全局变量x的了,因为在当前的rain()函数内已经有局部变量x。所以alert出1,
但为什么第一次alert出undefined呢?这可能和预解析有关。在代码执行时,首先会解析出变量和函数的定义,上述代码等价于下面的代码。

function rain(){
    var x;
    alert( x );
    x = '10';
    alert( x );
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容