由一道题图解JavaScript的作用域

作用域

为了明白作用域,跪看了好几篇大神的博文,终究略知一二。

1.问题

个中,看到如许一道题(稍作修正):

 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

虽然在不明白作用域的情况下,我也做出了准确答案。但当我看了网上大神对作用域的解说后,再来看此题,越看越典范,当初能做对,地道命运运限。当我将此题的作用域链画出来今后,终究觉得作用域入门了。

2.图解作用域

(1)JS引擎在进入一段可实行的代码时,须要完成以下三个初始化事情:

  • 建立一个全局对象(Global Object):全局对象在建立时,将Math,String,Date,document 等经常使用的JS对象作为其属性。另有别的一个属性window,将window指向了本身,如许就能够经由过程window接见这个全局对象了。

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

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

  • 建立一个与EC关联的全局变量对象(Varibale Object) VO,并把VO指向全局对象。VO中不仅包括了全局对象的原有属性,还包括在全局定义的变量和函数。同时,在定义函数A的时刻,还为 A 添加了一个内部属性scope,并将scope指向了VO。每一个函数在定义的时刻,都邑建立一个与之关联的scope属性,scope老是指向定义函数时地点的环境。
    《由一道题图解JavaScript的作用域》

(2)当实行进入app(‘eve’) 时

  • 建立函数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的全部作用域链。
    《由一道题图解JavaScript的作用域》

(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的全部作用域链。
    《由一道题图解JavaScript的作用域》

(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)的属性。
    《由一道题图解JavaScript的作用域》

实行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会被推入作用域链的顶端。

参考文献

    原文作者:small2
    原文地址: https://segmentfault.com/a/1190000004676747
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞