资讯专栏INFORMATION COLUMN

由一道题图解JavaScript的作用域

nevermind / 2707人阅读

摘要:当我将此题的作用域链画出来之后,终于感觉作用域入门了。创建函数的作用域链,并初始化为函数的所包含的对象,即包含了的作用域链。

作用域

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

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总是指向定义函数时所在的环境。

(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的整个作用域链。

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

参考文献

javascript 从定义到执行,你不知道的那些事

Javascript作用域原理

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79009.html

相关文章

  • 一道JS面试所引发"血案",透过现象寻本质,再从本质看现象

    摘要:一看这二逼就是周杰伦的死忠粉看看控制台输出,确实没错就是对象。从根本上来说,作用域是基于函数的,而执行环境是基于对象的例如全局执行环境即全局对象。全局对象全局属性和函数可用于所有内建的对象。全局对象只是一个对象,而不是类。 觉得本人写的不算很烂的话,可以登录关注一下我的GitHub博客,博客会坚持写下去。 今天同学去面试,做了两道面试题,全部做错了,发过来给我看,我一眼就看出来了,因为...

    QiShare 评论0 收藏0
  • JavaScript 作用作用链学习

    摘要:作用域与作用域链每个函数都有自己的执行环境。这是初步了解作用域,如想更深入了解作用域,请看下面链接作用域原理作用域链由一道题图解的作用域或者看权威指南和高级程序设计 本文是我学习JavaScript作用域整理的笔记,如有不对,请多指出。 作用域 一个变量的作用域是程序源代码中定义这个变量的区域。 而在ES5中只分为全局作用域和函数作用域,也就是说for,if,while等语句是不会创建...

    史占广 评论0 收藏0
  • 深入理解JavaScript(二):一道来思考闭包

    摘要:中所有的事件绑定都是异步编程当前这件事件没有彻底完成,不再等待,继续执行下面的任务当绑定事件后,不需要等待执行,继续执行下一个循环任务,所以当我们点击执行方法的时候,循环早已结束即是最后。 概念 闭包就是指有权访问另一个函数作用域中的变量的函数 点击li标签弹出对应数字 0 1...

    曹金海 评论0 收藏0
  • 一道引申出事件循环、letvar用法、iife、块级作用

    摘要:和块级作用域实际上为新增了块级作用域。这表示外层代码块不受内层代码块的影响。块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式不再必要了。其他骚气方法参考阮老师并发模型与事件循环 没有错,这道题就是: for (var i = 0; i< 10; i++){ setTimeout(() => { console.log(i); }, 1000...

    animabear 评论0 收藏0
  • Deep in JS - 收藏集 - 掘金

    摘要:今天同学去面试,做了两道面试题全部做错了,发过来给道典型的面试题前端掘金在界中,开发人员的需求量一直居高不下。 排序算法 -- JavaScript 标准参考教程(alpha) - 前端 - 掘金来自《JavaScript 标准参考教程(alpha)》,by 阮一峰 目录 冒泡排序 简介 算法实现 选择排序 简介 算法实现 ... 图例详解那道 setTimeout 与循环闭包的经典面...

    enali 评论0 收藏0

发表评论

0条评论

nevermind

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<