JavaScript高级程序设计(第3版)学习笔记 第7章

第7章 函数表达式

1.定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。

函数声明:一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。

//不会抛出错误,因为在代码执行之前会先读取函数声明。
sayHi();
function sayHi(){
    alert("Hi!");
}

函数表达式:一种非常有用的技术,无须对函数命名, 从而实现动态编程。如下创建一个函数并将它赋值给变量 functionName。 这种情况下创建的函数叫做匿名函数(anonymous function),也叫拉姆达函数。

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

函数表达式与其他表达式一样,在使用前必须先赋值

sayHi(); //错误:函数还不存在 
var sayHi = function(){
    alert("Hi!");
};

2.递归函数是在一个函数通过名字调用自身的情况下构成的,如果在无法确定如何引用函数的情况下,就会变得比较复杂,所以应该始终使用 arguments.callee (一个指向正在执行的函数的指针)来递归地调用自身,不要使用函数名——函数名可能会发生变化。

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

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

  • 当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链(作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象)。
  • 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
  • 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
  • 但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。

过度使用闭包可能会导致内存占用过多,只在绝对必要时再考虑使用闭包。

4.闭包只能取得包含函数中任何变量的最后一个值(闭包所保存的是整个变量对象,而不是某个特殊的变量)。

/*函数会返回一个函数数组,每个函数都返回 10。
因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,
所以它们引用的都是同一个变量 i。
当 createFunctions()函数返回后,变量 i 的值是 10,
此时每个函数都引用着保存变量 i 的同一个变量 对象,所以在每个函数内部 i 的值都是 10。*/
function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i; 
        };
    }
    return result;
}

我们可以通过创建另一个匿名函数强制让闭包的行为符合预期

/*定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。
在调用每个匿名函数时,我们传入了变量 i。
由于函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num。
而在这个匿名函数内部,又创建并返回了一个访问 num 的闭包。
这样一来,result 数组中的每个函数都有自己 num 变量的一个副本,因此就可以返回各自不同的数值了。*/
function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            }; 
        }(i);
    }


}

5.匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。(在通过 call()或 apply()改变函数执行环境的情况下,this 就会指向其他对象。)

/*在定义匿名函数之前,我们把 this 对象赋值给了一个名叫 that 的变量。
在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声名的一个变量。
即使在函数返回之后,that 也仍然引用着 object,所以调用 object.getNameFunc()()就返回了"My Object"。*/
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        }; 
    }
};
alert(object.getNameFunc()());  //"My Object"

6.JavaScript 没有块级作用域的概念,匿名函数可以用来模仿块级作用域,经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。

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

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

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

8.在使用构造函数模式实现自定义类型的特权方法,创建 MyObject 的实例后,只有使用 publicMethod()可以直接访问 privateVariable 和 privateFunction()。

function MyObject(){

    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //特权方法
    this.publicMethod = function (){
        privateVariable++;
        return privateFunction();
    };
}
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据
缺点是针对每个实例都会创建同样一组新方法
//定义了两个特权方法:getName()和 setName()。这两个方法都可以在构 造函数外部使用,而且都有权访问私有变量 name。
//但在 Person 构造函数外部,没有任何办法访问 name。
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"

9.使用原型模式实现自定义类型的特权方法:创建一个私有作用域,在其中封装一个构造函数及相应的方法。首先定义私有变量和私有函数,然后定义构造函数及其公有方法,公有方法是在原型上定义的。定义构造函数时使用函数表达式,而且不使用var关键字(函数声明只能创建局部函数,初始化未经声明的变量,总是会创建一个全局变量。
缺点是在一个实例上调用 setName()会影响所有实例。而调用 setName()或新建一个 Person 实例都会赋予 name 属性一个新值。结果就是所有实例都会返回相同的值。

(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"

10.使用模块模式实现单例(指的就是只有一个实例的对象)的特权方法:使用一个返回对象的匿名函数。

在函数内部,首先定义私有变量和函数。 然后,将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。从本质上来讲,这个 对象字面量定义的是单例的公共接口。

这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。

var singleton = function(){
//私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
            return false;
    }
    //特权/公有方法和属性
    return {
        publicProperty: true,

        publicMethod : function(){
            privateVariable++;
            return privateFunction();
        }
    }; 
}();

11.使用增强的模块模式实现单例的特权方法,适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。

//如果要求必须是CustomType的实例
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;
}();

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值