Js 之 递归,闭包

下面创建一个经典 的 阶乘的递归函数:

     

arguments.callee()  是一个指向正在执行的函数的指针。

     

function factorial(num) { if (num <= 1) { return num; } else { return num * arguments.callee(num - 1); } } alert(factorial(3)); //6

     但是这样在严格模式下是不能用的,在严格模式下,如下调用:

    

var factorial = (function f(num) { //命名函数表达式 if (num <= 1) { return num; } else { return num * f(num - 1); } }); alert(factorial(3));

闭包

闭包 是指 有权访问另一个函数作用域中的变量的函数。下面看个例子:

function compareAge(age) { return function (object1, object2) { var v1 = object1.age; var v2 = object2.age; if (v1 < v2) { return -1; } else if (v1 > v2) { return 1 } else { return 0; } } }

变量v1,v2的赋值,调用了外部函数的参数。即使这个内部函数被反回了,而且是被其他地方调用了,

它仍然可以访问变量age, 之所以能够访问,是因为内部函数的作用域中包含外部函数的作用域。

当某个函数 第一次被调用,会创建一个 执行环境 及相应的作用域链,并把作用域链赋值给一个特殊的内部属性

[[Scope]] .然后,使用this,arguments和其他命名参数的值 来初始化函数的 活动对象 但在作用域链中,外部函数的活动对象

始终处于第二位,外部函数的外部函数处于第三位,一次类推。。。

在函数执行时,为读取和写入变量的值,就需要在 作用域链 中查找变量,看下面例子:

上述代码创建一个比较函数,之后再全局作用域中 调用它。

function compare(v1, v2) { if (v1 > v2) { return 1; } else { return 0; } } var result = compare(1, 2);//0

第一次调用 compare()时,会创建一个包含 this,arguments,v1,v2的活动对象。

全局执行环境的变量对象(包含 this,result,compare) 在compare() 执行环境的作用域链中则处于第二位。关系如下图:

                          image

每个执行环境都有 一个  变量对象 ,全局环境的变量对象 始终存在.而像 compare()函数这样的 局部环境的变量对象 ,则只在

函数执行过程中存在. 

那么在创建 compare()函数时,会创建一个 包含全局变量对象的作用域链, 这个作用域链 被保存在 [[scope]]属性内.

当调用 compare() 函数时,会为函数创建一个执行环境,然后通过复制 函数的 [[Scope]]属性中的对象 构建起执行环境的作用域链.

此后,又有一个活动对象 被创建,并推入执行环境作用域链的前端.

对于这个例子, compare()函数的执行环境而言,其作用域链中包含两个变量对象: 本地活动对象和全局变量对象.

显然,作用域链本质上 是一个指向变量对象的指针列表,它只引用但不实际包含变量对象.

 

无论什么时候在函数中访问一个变量时,就会从作用域中搜索具有相应名字的变量.一般来讲,当函数执行完毕后,局部活动 变量就会被销毁

内存中仅保存全局作用域.但是闭包 有所不同:

看下面函数:

//闭包函数 function compare(propertyName) { return function (object1, object2) { var v1 = object1[propertyName]; var v2 = object2[propertyName]; if (v1 > v2) { return 1; } else if (v1 < v2) { return 2; } else { return 0; } } } var person1 = { name: "li", age: 12 }; var person2 = { name: "ming", age: 14 }; var f = compare("age"); var result = f(person1, person2); alert(result); //2 alert(f); //函数f的全部代码 //普通函数 function asd() { document.getElementById("asd"); }; var s = asd(); alert(s); //undefined

 

在另一个函数内部定义的函数会将 包含函数(外部函数)的 活动对象添加到 它的作用域链中.

因此 compare()内部的构造函数 的作用域链将包含 compare()函数的活动对象.

在匿名函数从compare()中被返回后,它的作用域链就被初始化为 包含compare()函数的活动对象和全局变量对象.这样匿名函数就可

访问在 compare()中的所有变量. 更为重要的是,compare()函数在执行完毕后,其活动对象不会被销毁,因为匿名函数作用域仍然在引用这个活动对象.

换句话说,当compare()函数返回后,其执行环境的作用域链被销毁了,但他的活动对象 仍然保存在内存中,直到匿名函数被销毁,compare()函数的活动对象才被销毁.

看如下代码  (基于compare()函数的):

var person1 = { name: "li", age: 12 };
        var person2 = { name: "ming", age: 14 };
        var f = compare("age");
        var result = f(person1, person2);
        alert(result);  //2
        f = null;
        alert(f);  //undefined

 

上述代码 通过将f设置为 null,解除 该函数的引用,就等于通知垃圾回收历程,将其清除.随着匿名函数的作用域链被销毁,其他的作用域链也可以安全的销毁了(除了全局作用域). 

下图展示了 f()的过程中产生的作用域链之间的关系:

image

 

由于闭包会携带包含它的函数的作用域,因此会比其他函数更占用内存.

 

闭包与变量

作用域链这种配置机制 引出了一个值得注意的副作用,就是 闭包只能取得包含函数中任何变量的最后一个值,

因为闭包所保存的一个变量对象,而不是某个特殊的变量,看下面的例子说明这个:

function a() {
            var array = new Array();
            for (var i = 0; i < 6; i++) {
                array[i] = function () {
                    return i;
                };
            }
            return array;
        }
        var result;
        for (var j = 0; j < a().length; j++) {
            result += "\n" + "a()[" + j + "]:" + a()[j]();
        }
        alert(result);

result结果 如下图所示:

image

结果并没有和预想一样是 每个函数返回自己的索引.

因为每个函数的作用域中都保存着a()的活动对象,所以他们引用的都是同一个变量i,

当a()函数返回后,变量i的值为6, 此时每个函数都引用 着 保存变量i的同一个变量对象.所以每个函数内部i的值都是6.

但是可以通过 另一个匿名函数强制闭包的行为: 例子如下:

function a() {
            var array = new Array();
            for (var i = 0; i < 6; i++) {
                array[i] = function (num) {
                    return function () {
                        return num;
                    }
                };
            }
            return array;

        }
        var result;
        for (var j = 0; j < a().length; j++) {
            result += "\n" + "a()[" + j + "]:" + a()[j](j)();
        }
        alert(result);

结果如下图:

image

重写了a()函数,没有直接把闭包的复制给数组,而是定义了一个匿名函数.并将立即执行该匿名函数的结果赋值给数组.

这里的匿名函数有个参数num,也就是最终的函数要返回的值.在调用每个匿名函数时,我们传入了变量i,由于函数的参数是按值传递的,

所以就会将变量i的值复制给 参数num,而在这个匿名函数内部,有创建了并返回一个访问num的闭包.这样 array数组的每个函数都有自己的num变量的一个副本.

关于this对象

在闭包中使用this有可能 会导致一些问题.

this对象 是在运行时基于函数的执行环境绑定的. 在全局函数中, this等于window,而当被作为某个对象的方法调用时,this等于那个对象.

不过,匿名函数的执行环境具有全局性,因此其this 对象通常指向window, 看下面例子:

var name = "window";
        var object = {
            name: "gao",
            getName: function () {
                return function () {
                    return this.name;
                }
            }
        }

        alert(object.getName()());//非严格模式下  window

前面说过,每个函数被调用时,其活动对象都会自动取得两个特殊的变量,this和 arguments.内部函数在搜索这两个变量时,

只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量. 不过可以把外部作用域中的this对象保存在一个闭包

能够访问到的变量(that)里,就可以让闭包访问该对象(object)了:看下面例子:

var name = "window";
        var object = {
            name: "gao",
            getName: function () {
                var that = this;
                return function () {
                    return that.name;
                }
            }
        }
        alert(object.getName()()); //gao

提示:this和arguments 页存在同样的问题,如果想访问作用域中的arguments对象,必须将该对象的引用保存到另一个闭包可以访问的变量中.

内存泄露

如果闭包的作用域链中保存着一个html元素,那么就意味着该元素将无法销毁: 看下面例子:

(function () {
            var div = document.getElementsByTagName("div");
            var className = div[0].className;
            if (div.length > 0) {
                div[0].onclick = function () {
                    alert(className);
                };
            }
            div = null;  //解除对DOM对象的引用
        })();

 

在上面代码中,通过把 div className存在一个变量中,并且在 闭包中引用该变量 消除了循环引用.但仅仅这么做还是不能避免内存泄露.

需要把 div变量设置为null,从而解除对DOM对象的引用,减少了其引用数,确保内存能够正常的回收.

posted @ 2012-06-08 13:02  高捍得  阅读(3109)  评论(0编辑  收藏  举报