JS 7.1-7.2 递归&闭包

函数声明提升

函数声明可以放在调用语句的后面;

另,对变量声明,在es6中推荐使用let/const来避免变量声明提升;

匿名函数

也叫拉姆达函数,匿名函数的name属性是空字符串;

var functionName = function(arg0, arg1, arg2){
//函数体
};

递归

如何使递归出错

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

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出错!

如何解决这个错,安全递归

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

arguments.callee是一个指向正在执行的函数的指针,但是在严格模式下,不能通过脚本访问arguments.callee;

但是可以使用命名函数表达式来达成相同的结果;

var factorial = (function f(num){
    if (num <= 1){
        return 1;
    } else {
        return num * f(num-1);
    }
});

闭包

闭包是指有权访问另一个函数作用域中的变量的函数;

作用域链被保存在内部的[[Scope]]属性中

function createComparisonFunction(propertyName) {
    return function (object1, object2) {
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        if (value1 < value2) {
            return -1;
        } else if (value1 > value2) {
            return 1;
        } else {
            return 0;
        }
    };
}

//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({name: "Nicholas"}, {name: "Greg"});
//解除对匿名函数的引用(以便释放内存)
compareNames = null;

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

过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包;

虽然像V8等优化后的JavaScript 引擎会尝试回收被闭包占用的内存,但还是要慎重使用闭包;

一个经典的闭包例子

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}

实际上每个函数都返回10,因为它们的作用域链中都保存着createFunctions()函数的活动对象,引用的都是同一个i;

解决方法

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            };
        }(i);
    }
    return result;
}

这是一个自执行函数,里面包含了一个新的闭包,这样每个函数都有num的一个副本;

this对象

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

匿名函数的执行环境具有全局性,所以this对象通常指向window;

当然,在通过call()或apply()改变函数执行环境的情况下,this就会指向其他对象;

但是有时候,不大明显,如:

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

每个函数在被调用时都会自动取得两个特殊变量:this和arguments;

内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量;

但是呢,把外部作用域的this对象保存在闭包能够访问的变量里,那闭包也可以访问该对象了;

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function () {
        var that = this;
        return function () {
            return that.name;
        };
    }
};
alert(object.getNameFunc()()); //"My Object"

几种特殊情况下,this的值会意外地改变;

var name = "The Window";
var object = {
    name : "My Object",
    getName: function(){
        return this.name;
    }
};

object.getName(); //"My Object"
(object.getName)(); //"My Object"
(object.getName = object.getName)(); //"The Window",在非严格模式下

第三条呢,应该是相当于

var ttt = {
    name : "emem",
    getName: function() {
        return this.name;
    }
};
console.log((object.getName = ttt.getName)());

这样?

内存泄漏

闭包在ie9之前的问题,如果闭包的作用域链中保存着一个html元素,那么该元素无法销毁;

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

解决方案

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        alert(id);
    };
    element = null;
}

在上面的代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用;

但仅仅做到这一步,还是不能解决内存泄漏的问题,必须要记住:

闭包会引用包含函数的整个活动对象,而其中包含着element;

即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用;

因此,有必要把element变量设置为null;