JavaScript中函数的范围链

1、函数介绍

    函数在JavaScript中能够通过单一的调用而执行一组操作。在JavaScript中,函数(function)有几种不同的称谓,分别是:函数、方法和构造器,定义一个函数为某一个对象的属性,则称这个函数为方法,构造器则是函数与new操作符一起操作的函数。有以下几种定义函数的方式:

  •     函数 声明
function maximum(x,y){
    if(x > y)
        return x;
    else
        return y;
}
maximum(5,6) //Returns 6;

这种定义的方式在定义顶层函数时非常常用。

  •     function literals

 

var obj = new Object();
obj.maximum = function (x,y) {
                if(x > y)
                    return x;
                else
                    return y;
              };
obj.maximum(5,6) //Returns 6;

 这种定义方式在定义函数作为一个对象的方法时最常用。

  •     构造函数

 

var maximum = new Function ("x","y","if(x > y) return x; else return y;");

maximum(5,6);  //Returns 6;

  这种定义方式不常用。

2、函数定义

    函数定义指的是:在JavaScript引擎内定义一个函数对象。对于一个顶层函数,会把这个对象作为一个属性增加到全局对象中;对于一个内嵌的函数(定义在函数内的函数),则会把这个对象加到激活对象 (activation object) 中。顶层函数会在执行这个脚本之前定义,也就是说,在执行脚本之前,会为这个顶层函数创建函数对象,并加入到全局对象中。看下面的例子,一旦JavaScript解释器解析完毕开始执行下面的语句时,会为‘func’创建函数对象并作为一个属性加入到全局对象中:

/*函数'func'在这里可以被调用,因为func的定义在脚本开始执行时已经发生.*/
alert(func(2));  //Returns 8
//这个语句覆盖了func的值为true.
var func = true;
alert(func);     //prints true;
/*当脚本开始执行的时候,解析下面的语句并且会为函数‘func’定义函数对象。*/
function func(x){
      return x * x * x;
}

 而对于一个内嵌的函数,则函数定义过程发生在当调用外部函数的时候,会定义内嵌的函数,并为这个内嵌函数创建函数对象,加入到外部函数的激活对象(activation object)中。例如:

function outerFn()

{

    function innerFn()

    {

   

    }

}

 

outerFn(); //调用 outerFn 会引起innerFn的定义当outerFn 函数体开始执行的时候

 注意 :对于函数构造器来说,函数定义会发生在调用‘Function’构造器的时间。

3、范围链

    范围链指的是一个对象链,当在对象链中查找一个变量的时候,对象链的对象的属性能够被查找在JavaScript中,函数在定义之后就会有他们的范围链,这个范围链是在他们执行的时候的范围链,而不是在他们调用的时候的范围链 It is this scope chain in which they execute rather than the scope chain from which they are invoked. ,比如:

function outerFn(i){
    return function innerFn(){
        return i;
    }
}
var innerFn = outerFn(4);
innerFn();//Returns 4
 

 在掉用innerFn的时候,变量i并没有在innerFn内部定义,也没有在全局对象中定义,但是这句话是可以执行的,返回值为4,那么这个i是从哪来的呢?下面从顶层函数来慢慢分析:

3.1 全局函数

    顶层函数的执行范围链是非常直接的:

var x = 10;

var y = 0;

function testFn(i){

    var x = true;

    y = y + 1;

    alert(i);

}

testFn(10);

 Figure 1:全局函数的范围链

  • Global Object:在JavaScript引擎开始执行代码的时候,会创建一个全局对象,并初始化全局对象一些预定义的值,像 ‘Infinity’, ‘Math’等。脚本中的全局变量只是定义在这个全局对象中的属性,像上面代码中的x呵y变量。
  • Activation Object:当JavaScript引擎调用任何的函数的时候,它会创建一个新的对象,也就是Activation Object激活对象,然后将在函数内部定义的变量以及通过arguments传递进来的参数都会被定义到这个激活对象中。然后将这个对象加入到执行范围链的前端。

 3.2 内嵌函数

    对于内嵌函数,范围链的形成比较复杂,看下面的例子:

function outerFn(i,j)

{

    var x = i + j;

    return function innerFn(x)

    {

        return i + x;

    }

}

 

var func1 = outerFn(5,6);

var func2 = outerFn(10,20);

 

alert(func1(10));//Returns 15

alert(func2(10));//Returns 20

 能够看到,在分别调用func1(10)和func2(10)的时候,变量i具有不同的值,下面一步步分析:

    在下面的语句执行的时候

var func1 = outerFn(5,6);

 会发生三个个动作,一是创建outerFn函数的激活对象(Activation Object);然后定义innerFn函数,并且作为outerFn的激活对象的一个属性加入到 outerFn的激活对象中;因为每个函数在定义的时候都会有它自己的范围链,并且这个范围链是执行范围链,这个时候的执行范围链包含outerFn的激活对象以及全局对象。然后会把这个执行范围链为内嵌函数innerFn保存起来。执行完毕后,返回内嵌对象,并用func1来引用。

    当执行这条语句时:

   

alert(func1(10));//Returns 15

     创建func1的激活对象,并且加入到已经保存的执行范围链的最前端,就是在这个执行范围链下,func1开始执行。这样在这个范围链下,i属性就解决了。看下图,能表现的更清楚,对于func1的调用时候的范围链,如图所示:

对于func2的执行范围链,看下图:

 

从这两个图就能很清晰的看出来在func1呵func2两个方法调用的时候,i的值是不一样的。

4 激活对象与范围链

    是不是在函数调用的时候,创建这个函数的激活对象,然后在函数执行完毕之后,这个激活对象就会被销毁呢?为了解答这个问题,下面根据例子来说明一下激活对象与范围链的关系。

    4.1 没有内嵌函数的情况

    看下面的例子:

 

function outerFn(x){

   return x*x;

}

var y = outerFn(2);

 没有内嵌函数,在调用函数时,会创建这个函数的激活对象,并把这个激活对象加入到执行范围链的最前端,需要注意的是,范围链是 唯一持有 这个激活对象的对象。当函数执行完毕之后,就把这个激活对象从执行范围链中删除,由于没有别的对象再引用这个激活对象了,因此这个激活对象会被垃圾回收器回收。

    4.2 有内嵌函数,但是内嵌函数没有被其包围函数之外的对象引用

   

function outerFn(x)

{

    //No reference for 'square' outside 'outerFn'

    function square(x)

    {

        return x*x;

    }

     

    //No reference for 'cube' outside 'outerFn'

    function cube(x)

    {

        return x*x*x;

    }

    var temp = square(x);

    return temp/2;

}

var y = outerFn(5);

    在一个函数有内嵌函数,并且内嵌函数没有被其外围函数之外的对象引用的时候,当调用外围函数时,这个函数的激活对象被创建,并将其加入到执行范围链的最前端,并且同样会被起内嵌函数的已保存范围链引用(在外围函数调用时,内嵌函数开始定义,内嵌函数定义的时候,会为其创建范围链),当这个外围函数退出的时候,它的激活对象从执行范围链中被删除。由于外围函数的激活对象和其内嵌对象是相互引用的,并且这两个对象都没有被外部的对象所引用,因此会被垃圾回收器回收。

4.3 内嵌函数被包围函数的外部对象引用

// 例子1
function outerFn(x)                            

{

    //Inner function referenced outside through return

   //value of 'outerFn'

       return function innerFn()

       {

           return x*x;

       }

   }

 

   //Reference to inner function returned.

   var square = outerFn(5);

square();
例子2
var square;

function outerFn(x)

{

//Inner function referenced outside through global variable

//'square'

    square = function innerFn()

    {

        return x*x;

    }

}

 

outerFn(5);

square();
 

 在具有内嵌函数,并且内嵌函数被外围对象引用的时候,当执行这个外部函数时,这个外部函数的激活对象被创建,并且把激活对象加入到执行范围链的最前端,同时,这个对象被内嵌函数的已保存的范围链所引用(在内嵌函数定义的时候会保存一个范围链)。当外部函数退出,这个激活对象会从执行范围链中被删除,但是它仍然在内嵌函数保存的范围链中有一个引用。由于有个外部的引用指向内嵌函数,因此这个激活对象一直保存在内嵌函数的已保存范围链中,并且在内嵌函数执行的时候可以被访问。

5 闭包和循环引用

    这里讲一下由于闭包而引起的循环引用内存泄漏。闭包通常指的是内嵌的函数可以在其包围函数的外部调用,下面给出一个小例子:

function outerFn(x)

{

    x.func = function innerFn()

             {

             }

}

 

var div = document.createElement("DIV");

outerFn(div);

 通过这个例子我们能够观察到在DOM对象和JS对象之间的循环引用被创建。DOM对象div通过func属性指向内嵌的函数innerFn,而 内嵌的函数innerFn也有一个指向DOM对象div的引用x,这个x是保存在outerFn的激活对象中,并且这个激活对象被保存到内嵌函数的已保存范围链中。

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

注意 :这篇文章翻译自MSDN的一篇文章,自己做了些加工与说明。可以点击这里 查看原文

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值