JavaScript高级程序设计——第7章:函数表达式

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

关于函数声明,它的重要特征:函数声明提升(function declaration hosting)

<!DOCTYPE html>
<html>
    <head>
        <title>Function Name Example</title>
    </head>
    <body>
        <script type="text/javascript">
            //函数声明
            function functionName(){
                //noop
            }
            
            //works only in Firefox, Safari, Chrome, and Opera
            alert(functionName.name); //"functionName"
           
           //函数声明提升
            sayHi();
            function sayHi(){
                alert("Hi!");
            }
           
           var fname=function(arg0,arg1){
           //函数体 这种情况下创建的函数叫做匿名函数(有时候也叫拉姆达函数),因为function后面没有标识符。匿名函数的name属性是空字符串。
           }
        </script>
     
    </body>
</html>

以下代码在ECMAScript中属于无效语法,如果是使用函数表达式就没有问题。

<!DOCTYPE html>
<html>
<head>
    <title>Function Declaration Error Example</title>
</head>
<body>
    <script type="text/javascript">
    
    var condition = true;
    
    //never do this!
    if(condition){
        function sayHi(){
            alert("Hi!");
        }
    } else {
        function sayHi(){
            alert("Yo!");
        }
    }

    sayHi();
    </script>
 
</body>
</html>

 把函数当成值来使用的情况下,都可以使用匿名函数。

7.1 递归 

经典的递归阶乘函数,虽然表面上看没有问题,但下面的代码可能导致它出错。

<!DOCTYPE html>
<html>
    <head>
        <title>Recursion Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function factorial(num){
                if (num <= 1){
                    return 1;
                } else {
                    return num * factorial(num-1);
                }
            }

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


        </script>
     
    </body>
</html>

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

<!DOCTYPE html>
<html>
    <head>
        <title>Recursion Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function factorial(num){
                if (num <= 1){
                    return 1;
                } else {
                    return num * arguments.callee(num-1);
                }
            }

            var anotherFactorial = factorial;
            factorial = null;
            alert(anotherFactorial(4));  //24


        </script>
     
    </body>
</html>

如下方式在严格模式和非严格模式都行得通

<!DOCTYPE html>
<html>
    <head>
        <title>Recursion Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
           factorial=(function f(num){
                if (num <= 1){
                    return 1;
                } else {
                    return num * f(num-1);
                }
            })

            var anotherFactorial = factorial;
            factorial = null;
            alert(anotherFactorial(4));  //24


        </script>
     
    </body>
</html>

 7.2 闭包

如何创建作用域链以及作用域链有什么作用的细节,对彻底理解闭包至关重要。当某个函数第一次被调用时,会创建一个执行环境(execution context)及相应的作用域链,并把作用域链赋值给一个特殊的内部属性(即[[Scope]])。然后,使用this、argument和其它命名参数的值来初始化函数的活动对象(activetion object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链终点的全局执行环境。后台的每个执行环境都有一个表示变量的对象---变量对象。全局的变量对象始终存在。而局部环境的变量对象,则只在函数执行的过程中存在。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

闭包是指有权访问另一个函数作用域中的变量的函数(函数调用返回后一个没有释放资源的栈区),创建闭包的常见方式,就是在一个函数内部创建另一个函数。

由于闭包会携带包含它的函数的作用域,因此比其他函数占用更多的内存。建议只在绝对必要时考虑使用闭包。

闭包的作用:一是可以读取函数内部的变量;二是让这些变量的值始终保持在内存中。

7.2.1 闭包与变量

作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量,下例可以清淅的说明问题:

<!DOCTYPE html>
<html>
    <head>
        <title>Closure Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function createFunctions(){
                var result = new Array();                
                for (var i=0; i < 10; i++){
                    result[i] = function(){
                        return i;
                    };
                }
                
                return result;
            }
            
            var funcs = createFunctions();//此时变量i的值是10
            
            //every function outputs 10
            for (var i=0; i < funcs.length; i++){
                document.write(funcs[i]() + "<br />");
            }

        </script>
        表面上看好像每个函数都应该返自己的索引值,但实际上每个函数都返回10,因为第个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们都引用的同一个变量i,当createFunctions()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以每个函数的内部i的值都是10。
</body> </html>
<!DOCTYPE html>
<html>
    <head>
        <title>Closure Example 2</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function createFunctions(){
                var result = new Array();
                
                for (var i=0; i < 10; i++){
                    result[i] = function(num){
                        return function(){
                            return num;
                        };
                    }(i);//这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i,由于函数参数是按值传递的,所以就会将变量i的当前值复制给参数num。而这个匿名函数内部又创建和并返回了一个访问num的闭包。这样一来result数组中的每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值了。
                }
                
                return result;
            }
            
            var funcs = createFunctions();
            
            //every function outputs 10
            for (var i=0; i < funcs.length; i++){
                document.write(funcs[i]() + "<br />");
            }

        </script>
     
</body> </html>

 7.2.2 关于this对象

每个函数在被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。arguments和this一样,也存在同样的问题。如果想访问作用域中的this(arguments)对象,必须将对该对象的引用保存到另一个闭包能够访问的对象里。匿名函数的执行环境具有全局性,因此其this对象通常指向window。

<!DOCTYPE html>
<html>
<head>
    <title>This Object Example</title>
</head>
<body>
    <script type="text/javascript">
        var name = "The Window";
        
        var object = {
            name : "My Object",
        
            getNameFunc : function(){
                return function(){
                    return this.name;
                };
            }
        };
        
        alert(object.getNameFunc()());  //"The Window"


    </script>
 
</body>
</html>
<!DOCTYPE html>
<html>
    <head>
        <title>This Object Example 2</title>
    </head>
    <body>
        <script type="text/javascript">
        
            var name = "The Window";
            
            var object = {
                name : "My Object",
            
                getNameFunc : function(){
                    var that = this;// 把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
                    return function(){
                        return that.name;
                    };
                }
            };
            
            alert(object.getNameFunc()());  //"MyObject"


        </script>
     
    </body>
</html>
<!DOCTYPE html>
<html>
    <head>
        <title>This Object Example 3</title>
    </head>
    <body>
        <script type="text/javascript">
        
            var name = "The Window";
            
            var object = {
                name : "My Object",
            
                getName: function(){
                    return this.name;
                }
            };
            
            alert(object.getName());     //"My Object"
            alert((object.getName)());   //"My Object"
            alert((object.getName = object.getName)());   //"The Window" in non-strict mode

        </script>
     
    </body>
</html>

7.3 模仿块级作用域

用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示(这种技术经常在全局作用域中被用在函数外部,从而限制在全局作用域中添加过多的变量和函数):

(fuction(){

//这里是块级作用域

})()

定义并立即调用了一个匿名函数

fuction(){

//这里是块级作用域

}()//出错!导致语法错误的原因是因为javascript把fuction当作函数声明的开始,而函数声明后面不能跟圆括号。

无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:

<!DOCTYPE html>
<html>
    <head>
        <title>Block Scope Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function outputNumbers(count){
            
                (function () {
                    for (var i=0; i < count; i++){
                        alert(i);
                    }
                })();
                
                alert(i);   //causes an error 在匿名函数中定义的任何变量,都会在函数执行结束时被销毁
            }

            outputNumbers(5);
        </script>
     
    </body>
</html>

一般来说我们应该尽量少向全局作用域中添加变量和函数,在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,开发人员既可以使用自己的变量,又不怕搞乱全局作用域。

(function(){

     var now=new Date();

     if(now.getMonth()==0&&now.getDate==1){

         alert("happy new year!");

     }

  })(); 

这种作法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。

 7.4 私有变量

 严格来讲,javascript中没有私有成员的概念,所有对象属性都是公有的,不过倒是有一个私有变量的概念。私有变量包括:函数的参数、局部变量、在函数内部定义的其它函数(构造函数里定义的function,即为私有方法)。利用闭包可以创建用于访问私有变量的公有方法,把有权访问私有变量和私有函数的公有方法称为特权方法,有两种创建特权方法的方式: 

var Person = function(name,sex){
    this.name = name;
    this.sex = sex;     
    var _privateVariable = "";//私有变量    
    //构造器中定义的方法,即为私有方法
    function privateMethod(){   
        _privateVariable = "private value";
        alert("私有方法被调用!私有成员值:" + _privateVariable);             
    }
    privateMethod(); //构造器内部可以调用私有方法            
}
 
Person.prototype.sayHello = function(){
    alert("姓名:" + this.name + ",性别:" + this.sex);
}
 
var p = new Person("Nicholas",""); 
p.sayHello();

//p.privateMethod();//这里将报错,私成方法无法被实例调用
alert(p._privateVariable);//显示: undefined

说明:类的构造函数里定义的function,即为私有方法;而在构造函数里用var声明的变量,也相当于是私有变量。(不过类比于c#这类强类型语言中的私有成员概念还是有区别的,比如无法在非构造函数以外的其它方法中调用) 

类似的,我们还能实现类似set,get属性的封装

<!DOCTYPE html>
<html>
    <head>
        <title>Privileged Method Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            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"


        </script>
     
    </body>
</html>

在构造函数中定义特权方法也有一个缺点,那就是必须使用构造函数模式来达到这个目的。使用静态私有变量特权方法就能解决这个问题。

7.4.1 静态私有变量 

<!DOCTYPE html>
<html>
    <head>
        <title>Privileged Method Example 2</title>
    </head>
    <body>
        <script type="text/javascript">
        
            (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"

        </script>
     
    </body>
</html>

 这个例子里name变成了一个静态的、由所有实例共享的属性。以这种方式创建静态私有变量会因为使用原型增进代码利用,但每个实例都没有自己的私有变量。到底是使用实例变量,还是静态私有变量,最终视具体需求而定。

7.4.2 模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法的。模块模式则是为单例创建私有变量和特权方法。所谓单例,就是只有一个实例的对象。

<!DOCTYPE html>
<html>
    <head>
        <title>Module Pattern Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function BaseComponent(){
            }
            
            function OtherComponent(){
            }
        
            var application = function(){
            
                //private variables and functions
                var components = new Array();
            
                //initialization
                components.push(new BaseComponent());
            
                //public interface
                return {
                    getComponentCount : function(){
                        return components.length;
                    },//返回已注册的组件数目
            
                    registerComponent : function(component){
                        if (typeof component == "object"){
                            components.push(component);
                        }
                    }//后者用于注册新组件
                };
            }();

            application.registerComponent(new OtherComponent());
            alert(application.getComponentCount());  //2
        </script>
     
    </body>
</html>

在Web应用程序中,经常需要使用一个单例来管理应用程序级的信息。这个简单的例子创建了一个用于管理组件的application对象。简言之,如果必须要创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是Object的实例,因为最终要通过一个对象字面量来表示它。事实上这也没什么,毕竟单例通常都是作为全局对象存在的,我们不会将它传递给一个函数,因此也没有必要用instansof操作符来检查它的对象类型。

7.4.3 增强的模块模式

<!DOCTYPE html>
<html>
    <head>
        <title>Module Pattern Example</title>
    </head>
    <body>
        <script type="text/javascript">
        
            function BaseComponent(){
            }
            
            function OtherComponent(){
            }
        
            var application = function(){
            
                //private variables and functions
                var components = new Array();
            
                //initialization
                components.push(new BaseComponent());
            
                //create a local copy of application
                var app = new BaseComponent();
            
                //public interface
                app.getComponentCount = function(){
                    return components.length;
                };
            
                app.registerComponent = function(component){
                    if (typeof component == "object"){
                        components.push(component);
                    }
                };
            
                //return it
                return app;
            }();

            alert(application instanceof BaseComponent);
            application.registerComponent(new OtherComponent());
            alert(application.getComponentCount());  //2
        </script>
     
    </body>
</html>

这种模式适合那些单例必须是某种类型的实例,同时必须添加某些属性和(或)方法对其加以增强。

7.5 小结

javascript的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。

原文地址:https://www.cnblogs.com/SmileX/p/5777229.html