由一道题图解JavaScript的做用域

做用域

为了理解做用域,跪看了好几篇大神的博文,终于略知一二。javascript

1.题目

其中,看到这样一道题(稍做修改):html

function factory() {
     var name = 'laruence';
     var intro = function(){
          console.log('I am ' + name);
          console.log('I am ' + age + "years");
     }
     return intro;
}
 
function app(para){
     var name = para;
     var age = 20;
     var func = factory();
     func();
}
 
app('eve');

运行结果是:前端

I am laruence
脚本报错:Uncaught ReferenceError: age is not defined

虽然在不理解做用域的状况下,我也作出了正确答案。但当我看了网上大神对做用域的讲解后,再来看此题,越看越经典,当初能作对,纯粹运气。当我将此题的做用域链画出来以后,终于感受做用域入门了。java

2.图解做用域

(1)JS引擎在进入一段可执行的代码时,须要完成如下三个初始化工做:web

  • 建立一个全局对象(Global Object):全局对象在建立时,将Math,String,Date,document 等经常使用的JS对象做为其属性。还有另一个属性window,将window指向了自身,这样就能够经过window访问这个全局对象了。app

  • 构建一个执行环境栈( Execution Context Stack):当执行一个函数时,该函数的执行环境就会被推入执行环境栈的顶部并获取执行权。当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除,并把执行权并还给以前执行环境。函数

  • 建立一个全局执行环境(Execution Context)EC,并将这个全局执行环境EC压入执行环境栈中。this

  • 建立一个与EC关联的全局变量对象(Varibale Object) VO,并把VO指向全局对象。VO中不只包含了全局对象的原有属性,还包括在全局定义的变量和函数。同时,在定义函数A的时候,还为 A 添加了一个内部属性scope,并将scope指向了VO。每一个函数在定义的时候,都会建立一个与之关联的scope属性,scope老是指向定义函数时所在的环境。
    图片描述spa

(2)当执行进入app('eve') 时code

  • 建立函数app的执行环境EC(a):而后EC(a)推入执行环境栈的顶部并获取执行权。

  • 建立函数app的做用域链(Scope Chain):在javascript中,每一个执行环境都有本身的做用域链,用于标识符解析,当执行环境被建立时,它的做用域链就初始化为当前运行函数的scope所包含的对象。

  • 建立一个当前函数的活动对象(Activation Object) AO:AO中包含了函数的形参、arguments对象、this对象、以及局部变量和内部函数的定义,而后AO会被推入做用域链的顶端。须要注意的是,在定义函数B的时候,JS引擎一样也会为B添加了一个scope属性,并将scope指向了定义函数B时所在的环境,定义函数B的环境就是A的活动对象AO, 而AO位于链表的前端,因为链表具备首尾相连的特色,所以函数B的scope指向了A的整个做用域链。
    图片描述

(3)当执行进入factory() 时

  • 建立函数factory的执行环境EC(f):而后EC(f)推入执行环境栈的顶部并获取执行权。

  • 建立函数factory的做用域链Scope Chain(f):在javascript中,每一个执行环境都有本身的做用域链,用于标识符解析,当执行环境被建立时,它的做用域链就初始化为当前运行函数的scope所包含的对象。

  • 建立一个当前函数的活动对象AO(f):AO中包含了函数的形参、arguments对象、this对象、以及局部变量和内部函数的定义,而后AO(f)会被推入做用域链的顶端。须要注意的是,在定义函数intro的时候,JS引擎一样也会为intro添加了一个scope属性,并将scope指向了定义函数intro时所在的环境,定义函数intro的环境就是factory的活动对象AO(f), 而AO(f)位于链表的前端,因为链表具备首尾相连的特色,所以函数intro的scope指向了factory的整个做用域链。
    图片描述

(4)当执行进入func()时
函数factory被执行之后,返回了intro的引用,并赋值给了变量func,执行 func() 就至关于执行intro()。

  • 建立函数intro的执行环境EC(i),而后EC(i)推入执行环境栈的顶部并获取执行权。 此时执行环境栈中有三个执行环境,分别是全局执行环境、函数app的执行环境和函数intro,intro的执行环境在栈顶,全局执行环境在栈的底部。

  • 建立函数intro的做用域链Scope Chain(i),并初始化为函数intro的scope所包含的对象,即包含了factory的做用域链。

  • 建立函数intro的活动对象AO(i),并将intro的arguments对象和this对象做为AO(i)的属性。
    图片描述

执行func()时,它的做用域链为 VO(G ) <- AO(f) <- AO(i),因此

  • 读取name属性时,AO(i)对象上没有name属性,沿着做用域链到AO(f),读取到了name属性,值为"laruence";

  • 读取age属性时,其实你会发现只有AO(f)上定义了age属性,而AO(f)又不在做用域链上,因此此时会报脚本错误Uncaught ReferenceError: age is not defined,表示age属性未定义。这也从侧面证实了咱们上面的做用域链分析是正确的。

执行完成,退出。

3.Javascript中的做用域

  • JavaScript中的函数运行在它们被定义的做用域里,而不是它们被执行的做用域里。

  • 在JS中,每次调用一个函数的时候 ,就会进入一个函数内的做用域,当从函数返回之后,就返回调用前的做用域。

  • 在javascript中,每一个函数都有本身的执行环境,当执行一个函数时,该函数的执行环境就会被推入执行环境栈的顶部并获取执行权。当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除,并把执行权并还给以前执行环境。

  • 每一个函数在定义的时候,都会建立一个与之关联的scope属性,scope老是指向定义函数时所在的环境。

  • 在javascript中,每一个执行环境都有本身的做用域链,用于标识符解析,当执行环境被建立时,它的做用域链就初始化为当前运行函数的scope所包含的对象。

  • 有了做用域链, 在发生标识符解析的时候, 就会逆向查询当前scope chain列表的每个活动对象的属性,若是找到同名的就返回。找不到,那就是这个标识符没有被定义。

  • 全局对象(Global Object) , 这个对象全局只存在一份,它的属性在任何地方均可以访问,它的存在伴随着应用程序的整个生命周期。全局对象在建立时,Math,String,Date,document 等经常使用的JS对象做为其属性。

  • 活动对象(Activation Object) AO,这里的活动对象扮演着变量对象的角色,只是在函数中的叫法不一样而已(你能够认为变量对象是一个总的概念,而活动对象是它的一个分支), AO中包含了函数的形参、arguments对象、this对象、以及局部变量和内部函数的定义,而后AO会被推入做用域链的顶端。

参考文献

相关文章
相关标签/搜索