ife task0003学习笔记(一):JavaScript作用域

在学习JavaScript作用域概念之前,首先要明白几个概念:执行环境、变量对象、作用域链。

一、JavaScript执行环境(execution context):

在《Professional JavaScript for Web Developers》一书中写到:

The concept of execution context, referred to as contextfor simplicity, is of the utmost importance in JavaScript. The execution context of a variable or
function defines what other data it has access to, as well as how it should behave. Each execution context has an associated variable objectupon which all > of its defined variables and functions exist. This object is not accessible by code but is used behind the scenes to handle data.

在对应的《JavaScript高级程序设计》一书翻译是这样的:

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保> 存在这个对象中。虽然我们编写的代码无法访问这个对象,但是解析器在处理数据时会在后台使用它。
全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完后,该环境被销毁,保存在其中的 > 所有变量和函数定义也随之销毁。(全局执行环境直到应用程序退出-例如关闭网页或者浏览器时才会被销毁。

二、全局作用域和局部作用域

1.全局作用域(Global Scope):在代码中任何地方都能访问到的对象拥有全局作用域

(1)最外层函数和在最外层函数外面定义的变量拥有全局作用域

var authorName="山边小溪";
function doSomething(){
    var blogName="梦想天空";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(authorName); //山边小溪
alert(blogName); //脚本错误
doSomething(); //梦想天空
innerSay() //脚本错误

(2)所有末定义直接赋值的变量自动声明为拥有全局作用域

function doSomething(){
    var authorName="山边小溪";
    blogName="梦想天空";
    alert(authorName);
}
doSomething(); //山边小溪
alert(blogName); //梦想天空
alert(authorName); //脚本错误

变量blogName拥有全局作用域,而authorName在函数外部无法访问到。

(3)所有window对象的属性拥有全局作用域

一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等

2.局部作用域:和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所有在一些地方也会看到有人把这种作用域称为函数作用域,例如下列代码中的blogName和函数innerSay都只拥有局部作用域。

function doSomething(){
    var blogName="梦想天空";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(blogName); //脚本错误
innerSay(); //脚本错误

我们再来看一个具体的实例:

var color = "blue";
function changeColor(){
    var anotherColor = "red";
    function swapColors(){
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
        // 这里可以访问 color、anotherColor 和 tempColor
    }
    // 这里可以访问 color 和 anotherColor,但不能访问 tempColor
    swapColors();
}
// 这里只能访问 color
changeColor();

以上代码共涉及 3 个执行环境:全局环境、 changeColor() 的局部环境和 swapColors() 的局部
环境。全局环境中有一个变量 color 和一个函数 changeColor() 。 changeColor() 的局部环境中有一个名为 anotherColor 的变量和一个名为 swapColors() 的函数,但它也可以访问全局环境中的变量 color 。 swapColors() 的局部环境中有一个变量 tempColor ,该变量只能在这个环境中访问到。无论全局环境还是 changeColor() 的局部环境都无权访问 tempColor 。然而,在 swapColors() 内部则可以访问其他两个环境中的所有变量,因为那两个环境是它的父执行环境。图 4-3 形象地展示了前面这个例子的作用域链。

图中的矩形表示特定的执行环境。其中,内部环境可以通过作用域链访问所有的外部环境,但
外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都
可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个
执行环境。对于这个例子中的 swapColors() 而言,其作用域链中包含 3 个对象: swapColors() 的变量对象、 changeColor() 的变量对象和全局变量对象。 swapColors() 的局部环境开始时会先在自己的变量对象中搜索变量和函数名,如果搜索不到则再搜索上一级作用域链。 changeColor() 的作用域链中只包含两个对象:它自己的变量对象和全局变量对象。这也就是说,它不能访问swapColors() 的环境。

三、JavaScript作用域链(scope chain)

作用域链创建:

当代码在一个环境中执行时,会创建变量对象的一个作用域。

作用域链功能:

作用域链:保证对执行环境有权访问的所有变量和函数的有序访问。

作用域链顺序:

1. 作用域的前端,始终是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象:在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中不存在)

2. 作用域链中的下一个变量对象来自包含(外部)环境
3. 再下一个变量对象则来自下一个包含环境。
4. 这样,一直延续到全局执行环境,全局执行环境的变量对象始终都是作用域链中的最后一个对象。

解析

标识符解析是沿着作用域链一级一级地搜索标识符的过程。

搜索

搜索过程始终是从作用域的前端开始,逐级向后回溯,直至找到标识符为止(找不到,通常会报错)。

举例

任何执行上下文时刻的作用域, 都是由作用域链(scope chain)来实现.
在一个函数被定义的时候, 会将它定义时刻的scope chain链接到这个函数对象的[[scope]]属性.
在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 然后对于每一个函数的形参,都命名为该活动对象的命名属性, 然后将这个活动对象做为此时的作用域链(scope chain)最前端, 并将这个函数对象的[[scope]]加入到scope chain中.

 var func = function(lps, rps){
          var name = 'laruence';
          ........
     }
     func();

解析
在执行func的定义语句的时候, 会创建一个这个函数对象的[[scope]]属性, 并将这个[[scope]]属性, 链接到定义它的作用域链上, 此时因为func定义在全局环境, 所以此时的[[scope]]只是指向全局活动对象window active object.

在调用func的时候, 会创建一个活动对象(假设为aObj, 由JS引擎预编译时刻创建),并创建arguments属性, 然后会给这个对象添加俩个命名属性aObj.lps, aObj.rps; 对于每一个在这个函数中申明的局部变量和函数定义, 都作为该活动对象的同名命名属性.

然后将调用参数赋值给形参数,对于缺少的调用参数,赋值为undefined。

然后将这个活动对象做为scope chain的最前端, 并将func的[[scope]]属性所指向的,定义func时候的顶级活动对象, 加入到scope chain.

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

作用域其实是Function对象实例的一个内部属性【Scope】。函数被创建时候【Scope】这个内部属性包含了这个函数的作用域内所有对象的集合,这个集合叫做作用域链,作用域链决定了函数能访问到那些数据。

<script type="text/javascript">
function add(a,b)
{
    var sum = a+b;
    return sum;
}
</script>

 这是一个非常常见的函数,我们先定义好这个函数,记住先不执行它,当页面被加载,这个函数也会被初始化,记得前面我讲过,这样定义的函数是属于window的,加载页面时候会预编译的,这时候函数add的作用域链如下图:

四、执行上下文顺序

在进入执行上下文时候,会按照顺序执行下面的操作:

1.创建活动对象

活动对象是在进入执行上下文时候创建出来的,并且与新的执行上下文关联在一起。在初始化构造函数的时候,该对象包含一个arguments属性。激活对象在变量初始化也会用到。

2.创建作用域链

每一个函数都有一个内部属性[[scope]],它的值是一个包含多个对象的链。这个属性的具体指与函数的创建方式和代码中的位置有很大的关系。这一步的操作是把上一步创建的激活对象添加到函数的[[scope]]属性对应的链的前面。

3.变量初始化

对函数中需要用到的变量进行初始化。初始化时使用到的对象是前面创建的活动对象,不过这时还不能称作为变量对象。函数的实参、内部函数的局部变量是会被初始化
的。局部变量是在变量对象创建的过程中创建了同名的属性,这个属性值为undefined,在函数执行的过程中才会被真正的赋值。

posted @ 2015-05-05 20:15  青青flye  阅读(276)  评论(0编辑  收藏  举报