JavaScript高级程序设计读书笔记7

第7章 函数表达式

  • 定义函数的方式有两种:一种是函数声明,另一种是函数表达式
  • 函数声明的一个重要特征是函数声明提升,在执行代码之前会先读取函数声明,可以把函数声明放在调用它的语句后面
  • var functionName=function(arg0,arg1,arg2){ //函数体 };函数表达式情况下创建的函数叫做匿名函数,因为function关键字后面没有标识符。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //以下做法错误,因为函数声明提升
    if(condition){
    function sayHi(){
    alert("Hi");
    };
    }else{
    function sayYo(){
    alert("Yo");
    };
    }
    //以下做法正确
    var sayHi;

    if(condition){
    sayHi=function(){
    alert("Hi");
    };
    }else{
    sayHi=function(){
    alert("Yo");
    };
    }

递归

  • 在编写递归函数时,使用arguments.callee总比使用函数名更加保险
  • 但在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。可以通过使用命名函数表达式来达成相同的结果
    1
    2
    3
    4
    5
    6
    7
    var factorial=(function f(num){
    if(num<=1){
    return 1;
    }else{
    return num*f(num-1);
    }
    });

闭包

  • 闭包是指有权访问另一个函数作用域中的变量的函数
  • 创建闭包的常见方式,就是在一个函数内部创建另一个函数
  • 总的来说,当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。但作用域链中,外部函数的活动对象始终位于第二位,外部函数的外部函数的活动对象处于第三位……直至作用域链重点的全局执行环境。
  • 当创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中
  • 当调用函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象被创建并被推入执行环境作用域链的前端。
  • 一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。但是闭包情况不同
  • 在另一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function createCpmparisonFunction(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=createCpmparisonFunction("name");
    //调用函数
    var result=compareNames({name:"Nicholas"},{name:"Greg"});
    //解除对匿名函数的引用,以便释放内存
    compareNames=null;
  • 当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。

  • 由于闭包会携带包含它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

    闭包与变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function createFunctions(){
    var result = new Array();
    for (var i = 0; i < 10; i++){
    result[i] = function(){
    return i;
    };
    }
    return result;
    }
  • 以上代码每个函数都返回10

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function createFunctions(){
    var result = new Array();
    for (var i = 0; i < 10; i++){
    result[i] = function(num){
    return function(){
    return num;
    };
    }(i);
    }
    return result;
    }
  • 以上代码函数返回0,1,2……因为参数是按值传递的,所以会将变量i的当前值复制给参数num

  • function(){}()这样写的作用:1.使这段代码被载入时候自动执行。2.避免污染全局变量。

关于this对象

1
2
3
4
5
6
7
8
9
10
11
var name="The Window";
var object={
name:"My Object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};

alert(object.getNameFunc()()); //The Window
  • 每个函数被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象位置,因为永远不可能直接访问外部函数中的这两个变量。
  • 解决方法:把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var name="The Window";
    var object={
    name:"My Object",
    getNameFunc:function(){
    var that=this;
    return function(){
    return that.name;
    };
    }
    };

    alert(object.getNameFunc()()); //My Object

内存泄漏

  • 内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
  • 如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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,包含函数的的活动对象中也依然会保存一个引用,不能解决内存泄漏问题。

  • 所以一定要把element设置为null,这样就可以解除对DOM对象的引用,顺利减少其引用数,确保正常回收占用的内存

    模仿块级作用域

  • 面对的问题:JS中没有块级作用域,即使重新声明同一个变量,只会对后续声明视而不见
  • 解决方法:块级作用域(私有作用域)

    1
    2
    3
    (function(){
    //这里是块级作用域
    })();
  • 定义并立即调用了一个匿名函数,将函数声明包含在一堆圆括号中,实际上是一个函数表达式,其后的另一对圆括号会立即调用这个函数。

    1
    2
    3
    function(){
    //这里是块级作用域
    }(); //错误!
  • 函数声明后不能跟圆括号,但是函数表达式后面可以跟圆括号。将函数声明转换成函数表达式的方法是加上一对圆括号。

  • 在匿名函数中定义的任何变量,都会在执行结束时被销毁。
  • 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
  • 可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数还行完毕,就可以立即销毁其作用域链了。

    私有变量

  • 任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。
  • 特权方法:有权访问私有变量和私有函数的公有方法。
  • 有两种在对象上创建特权方法的方式:

    1.在构造函数中定义特权方法,在实例化后,除了使用特权方法没有任何方法可以访问私有变量和方法
    存在的问题:构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量实现特权方法可以避免这个问题。

1
2
3
4
5
6
7
8
9
10
function MyObject(){
var privateVariable=10;
function privateFunction(){
return false;
}
this.publicMethod=function(){
privateVariable++;
return privateFunction();
}
}

2.静态私有变量,所有实例共享私有变量和函数,在一个实例上改变变量会影响所有变量
存在问题:因为使用原型而增进代码的复用,每个实例都没有自己的私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
(function(){
var privateVariable=10;
function privateFunction(){
return false;
}
MyObject=function(){
};
MyObject.prototype.publicMethod=function(){
privateVariable++;
return privateFunction();
};
})();

模块模式

  • 模块模式:为单利创建私有变量和特权方法
  • 使用场景:必须创建一个对象,并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,就可以使用模块模式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var singleton=function(){
    var privateVariable=10;
    function privateFunction(){
    return false;
    }
    return {
    publicProperty:true,
    publicMethod:function(){
    privateVariable++;
    return privateFunction();
    }
    };
    }();

增强的模块模式

  • 使用场景:适合那些单例必须是某种类型的实例,同时还必须添加某些属性或方法对其记忆增强的情况。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var singleton = function () {
    var privateVariable = 10;

    function privateFunction() {
    return false;
    }

    var object = new CustomType();

    object.publicProperty = true;
    object.publicMethod = function () {
    privateVariable++;
    return privateFunction();
    };
    return object;
    }();