• 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的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。

  • 相关阅读:
    2019.2.18接口
    第一阶段复习
    vue-cli使用介绍
    Webpack 入门教程
    js报错Uncaught TypeError: Cannot read property 'getElementsByTagName' of null
    用shell编程.编写一个程序,用循环创建多个目录 并在该目录下创建多个文件 在文件中写入内容:
    explorer.exe应用程序错误,怎么解决?
    [Java连接MySQL数据库——含详细步骤和代码](https://www.cnblogs.com/town123/p/8336244.html)
    墨刀的简单使用
    laravel报错:SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '0' for key 'PRIMARY...
  • 原文地址:https://www.cnblogs.com/SmileX/p/5777229.html
Copyright © 2020-2023  润新知