JavaScript作用域学习笔记
2016-01-03 23:30
956 查看
一 执行环境
定义
执行环境(execution context)定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之相关的变量对象(variable object),执行环境中定义的所有变量和函数都保存在这个对象中。
执行模式
全局执行环境是最外围的一个执行环境。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数也随之销毁。对于全局执行环境直到应用程序退出时才会被销毁。每个函数都有自己的执行环境。当调用一个 JavaScript 函数时,该函数就会进入相应的执行环境。如果又调用了另外一个函数(或者递归地调用同一个函数),则又会创建一个新的执行环境,并且在函数调用期间执行过程都处于该环境中。当调用的函数返回后,执行过程会返回原始执行环境。因而,运行中的 JavaScript 代码就构成了一个执行环境栈。如下图所示:
<script type="text/javascript"> function Fn1(){ function Fn2(){ alert(document.body.tagName);//BODY //other code... } Fn2(); } Fn1(); //code here </script>
图片及代码来源:笨蛋的座右铭
二 作用域链(scope chain)
定义
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证执行环境有权访问的所有变量和函数的有序访问。作用域链的创建
在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。(1)当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。例如定义下面这样一个函数:
function add(num1,num2) { var sum = num1 + num2; return sum; }
在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只例举了全部变量中的一部分):
图片来源:梦想天空
正如前面语句(1)说到的那样,该函数在全局作用域中被创建,所以他的作用域链便被全局作用域中的数据对象填充了。
函数add的作用域将会在执行时用到,如下:
var total = add(5,10);
(2)执行此函数时会创建执行环境,每个执行环境都有自己的作用域链,用于标识符解析,当执行环境被创建时,而它的作用域链初始化为当前执行函数的[[Scope]]所包含的对象。
(3)这些值按照它们出现在函数中的顺序被复制到执行函数作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当执行环境被销毁,活动对象也随之销毁。新的作用域链如下图所示:
图片来源:梦想天空
标识符解析是沿着作用域链一级一级的搜索标识符的过程。 搜索过程始终从作用域链的前端开始,然后逐渐向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。
进一步理解作用域链
前面说明了很多散乱的定义概念,在此完整的梳理一下JS中函数的创建以及执行的整个过程中发生了什么。下面是一段代码:function a() { var name = 'Ma'; var b = function(){ alert('I am ' + name); } return b; } function c(para){ var name = para; var func = a(); func(); } c('Li');
创建函数a和函数c时,由语句(1)可知,函数a和c的作用域链情况如下:
[[scope chain]] = [ { Global object } ]
他们的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,[[scope]]只是指向全局活动对象。
由语句(2)(3)可知,调用函数c时,c函数的作用域链情况如下:
[[scope chain]] = [ { para : 'Li', name : undefined, func : undefined, arguments : [] }, { Global object } ]
调用过程创建了活动对象,他包含了函数的所有局部变量、命名参数、参数集合等,然后此对象会被推入作用域链的前端。
当调用进入a的函数体的时候, 此时的a的scope chain为:
[[scope chain]] = [ { name : undefined, b : undefined }, { Global object } ]
创建b函数时,由语句(1)知,b的scope chain为:
[[scope chain]] = [ { name : 'Ma', b : undefined }, { Global object } ]
接着函数a返回函数b给函数c,函数c调用b时,根据语句(2)(3)b的scope chain为:
[[scope chain]] = [ { b object }, { name : 'Ma', b : undefined }, { Global object } ]
name标识符解析的结果应该是a活动对象中的name属性, 也就是‘Ma’。
JS权威指南中有一句很精辟的描述:“JavaScript中的函数运行在他们被定义的作用域中,而不是他们被执行的作用域里。”
关于作用域还有其他一些需要注意的小地方,比如JS没有块级作用域,延长作用域u,作用域优化……不过细枝末节简明易懂没什么太多可说,所以还是写下这精华足矣。
作为个人学习笔记的第一篇,全是学习网上的各位大神的博客文章,受益匪浅,在此感谢!
参考:
JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
Javascript作用域原理 | 风雪之隅
《JavaScript高级程序设计》(第三版)
相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android java 与 javascript互访(相互调用)的方法例子
- Python动态类型的学习---引用的理解
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 2019年开发人员应该学习的8个JavaScript框架
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因
- Canvas 在高清屏下绘制图片变模糊的解决方法
- Redux系列02:一个炒鸡简单的react+redux例子
- JavaScript 各种遍历方式详解
- call/apply/bind 的理解与实例分享
- 如何创建对象以及jQuery中创建对象的方式