js递归和闭包

1.递归

递归函数式在一个函数通过名字调用自身的情况下构成的。

function factorial(num){
    if (num <= 1) {
        return 1;
    } else {
        return num * factorial(num - 1)
    }
}
factorial(5); // 120

这是一个经典的递归阶乘函数。虽然这个函数表面看起来没什么问题。但是下面的代码会让它出错

const anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(5)); // 报错

上面代码导致报错的原因是我们把factorial置为null以后。只有anotherFactorial对这个函数的引用。但是anotherFactorial引用的函数里有factorial(num - 1) 目前factorial为空了,所以才会出错。

优化版

arguments.callee是一个指向正在执行函数的指针,因此可以用它来实现对函数的递归调用

function factorial(num){
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1)
    }
}
factorial(5); // 120
arguments.callee调用问题

在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。

最优解

我们可以使用命名函数表达式来达成同样的效果。

const factorial = (function f(num){
    if (num <= 1) {
        return 1;
    } else {
        return num * f(num - 1)
    }
});
factorial(5); // 120

如上代码,我们创建了一个名字f()的命名函数表达式,然后将它赋值给变量factorial。这样即使把factorial赋值给另外一个变量,然后factorial等于null,递归依旧可以正确完成不会出错。这种方式在严格模式和非严格模式下都行得通。

2.闭包

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

function createCompare(name){
    return function(obj1,obj2){
        const value1 = obj1[name];
        const value2 = obj2[name];
        if (value1 < value2) {
            return -1;
        } else if(value1 > value2){
            return 1;
        } else {
            return 0;
        }
    }
}

以上代码就是一个闭包的例子。内部匿名函数可以访问外部createCompare函数的变量name。内部函数会将包含外部函数的活动对象(name)添加到它的作用域中。

// 创建函数
const compare = createCompare('myName');
// 调用函数
var result = compare({myName: 'Lee'},{myName: 'Bob'})
// 解除对匿名函数的引用(以便释放内存)
compare = null;

以上代码,在匿名函数从createCompare中返回后,它的作用域链被初始化为包含createCompare函数的活动对象和全局变量对象。这样匿名函数就可以访问在createCompare()中定义的所有变量。更为重要的是createCompare()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当createCompare()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然留在内存中;直到匿名函数被销毁后,createCompare()的活动对象才会被销毁。

所以我们在执行完函数后,将保存匿名函数的compare的变量设置为null解除该函数的引用。就等于通知垃圾回收例程将其清除。随着匿名函数的作用域被销毁,其他作用域(除了全局作用域)也都可以安全地销毁了。

上面代码中,createCompare的作用域链中包含全局变量对象、createCompare、以及createCompare的活动对象name和arguments以及变量提升的result。而匿名函数的作用域链中不仅仅包含了自己的活动对象arguments、obj1、obj2。还包含了createCompare的作用域链中的内容。

所以,由于闭包会携带包含它的函数的作用域,因此闭包会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以尽量不使用闭包。虽然像V8等优化后的js引擎会尝试回收被闭包占用的内存,但是还是要慎重使用。

3.内存泄漏

由于IE9之前的版本对JS对象和COM对象使用不同的垃圾收集例程,因此闭包在IE的这些版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。

function handler(){
    var element = document.getElementById('someElement');
    element.onclick = function(){
        alert(element.id)
    }
}

以上代码创建了一个作为element元素事件处理程序的闭包,而这个闭包又创建了一个循环引用。由于匿名函数保存了一个对handler()活动对象的引用。因此就会导致无法减少element的引用次数。只要匿名函数存在,elment的引用次数至少也是1,因此它所占用的内存就永远不会被回收

不过,这个问题可以通过稍微改写一下代码来解决

function handler(){
    var element = document.getElementById('someElement');
    var id = element.id;
    element.onclick = function(){
        alert(id)
    };
    element = null; // 解除引用
}

上面代码吧element.id的一个副本保存在了变量id中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一步,还是不能解决内存泄漏的问题。闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用。因此有必要把element变量设置为null。这样就能够解除对DOM对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。

你可能感兴趣的:(javascript,javascript,开发语言,ecmascript)