1

最近工作有点儿小忙,好些天没有更新了哈、
时间就像海绵里的水,挤一挤就有了,深有体会。
今天忙里偷闲,继续把笔记给大家献上。
华丽的分割线


第七章 函数表达式

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

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

这是个经典的递归阶乘函数,表面看没问题,但是如果:

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

使用argument.callee可以解决这个问题,argument.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用。

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

在严格模式下,不能通过脚本访问arguments.callee,会导致错误。不过可以使用命名函数表达式来达成相同的结果。

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

即便把函数赋值给了另一个变量,函数的名字f仍然有效。

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

this对象是在运行时基于函数的执行环境绑定的:
在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。
不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window,但由于编写闭包的方式不同,这一点可能不会那么明显。

var name = "the window";
var object = {
    name : "my object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()());  //"this window"(在非严格模式下)

由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会立即调用它返回的函数,结果就是返回一个字符串。
为什么匿名函数没有取得其包含作用域的this对象呢?
每个函数在被调用时,其活动对象都会自动取得两个特殊的变量: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对象赋值给一个名叫that的变量。而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声名的一个变量。即使在函数返回之后,that也仍然引用着object,所以调用object.getNameFunc()()就返回了"my object"

3.模范块级作用域
javascript没有块级作用域的概念。这就意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
function outputNumbers(count){

for(var i=0; i<count; i++){
    alert(i);
}
alert(i);  //计数

}
javascript从来不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见。匿名函数可以用来模仿块级作用域并避免这个问题。

(function(){
    //这里是块级作用域
})();
var someFunction = function(){
    //这里是块级作用域
};
someFunction();

通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域
(function(){

var now = new Date();
if(now.getMonth() == 0 && now.getDate() == 1){
    alert("Happy new year!");
}

})();
把上面这段代码放在全局作用域中,可以用来确定哪一天是1月1日,如果到了这一天,给用于显示一条祝贺新年的消息。其中的变量now现在是匿名函数中的局部变量,而我们不必再全局作用域中创建它。
这种做法可以减少闭包占用的内存空间,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。

4.私有变量
严格来讲,javascript中没有私有成员的概念;所有对象属性都是公有的。
不过倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量,私有变量包含函数的参数、局部变量和在函数内部定义的其他函数。
我们把有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方式。
第一种是在构造函数中定义特权方法,模式如下

function MyObject(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //特权方法
    this.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
}

在创建MyObject的实例后,除了使用publicMethod()这一个途径外,没有任何办法可以直接访问privateVariable和privateFunction()
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据

function Person(name){
    this.getName = function(){
        return name;
    };
    this.setName = function(value){
        name = value;
    };
}
var person = new Person("Nicholas");
alert(person.getName());    //"Nicholas"
person.setName("Greg");
alert(person.getName());    //"Greg"

定义了两个特权方法:getName()和setName()。这两个方法都可以在构造函数外部使用,而且都有权访问私有变量name。
由于这两个方法是在构造函数内部定义的,它们作为闭包能够通过作用域链访问name。
私有变量name在Person的每个实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。
缺点:构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题。

通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法

(function(){
    var name = "";
    Person = function(value){
        name = value;
    };
    Person.prototype.getName = function(){
        return name;
    };
    Person.prototype.setName = function(value){
        name = value;
    };
})();
var person1 = new Person("Nicholas");
alert(person1.getName());     //"Nicholas"
person1.setName("Greg");
alert(person1.getName());     //"Greg"

var person2 = new Person("Michael");
alert(person1.getName());     //"Michael"
alert(person2.getName());     //"Michael"

在一个实例上调用setName()会影响所有实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是所有实例都会返回相同的值。
多查找作用域链中的一个层次,就会在一定程度上影响查找速度。而这正是使用闭包和私有变量的一个显明的不足之处。

小结:
在JavaScript编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名,从而实现动态编程。以下总结了函数表达式的特点。

  • 函数表达式不同于函数声明,函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫做匿名函数。
  • 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;
  • 递归函数应该始终使用arguments.callee来递归地调用自身,不要使用函数名——函数名可能会发生变化。

当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下:

  • 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
  • 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
  • 但是,在函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存在闭包不存在为止。

闭包还可以用于在对象中创建私有变量,相关概念和要点如下

  • 即使JavaScript中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
  • 有权访问私有变量的公有方法叫做特权方法。
  • 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。

JavaScript中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过因为创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。

春困秋乏啊,再啃看过的书,容易犯困啊,今天就记录到这儿,各位晚安~


前端小饼干
1 声望4 粉丝