您的位置:首页 > Web前端 > JavaScript

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高级程序设计》(第三版)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息