zoukankan      html  css  js  c++  java
  • 《JavaScript高级程序设计》学习笔记(第七章)

    函数表达式是JavaScript当中一个既强大又令人困惑的特性,特别是其中涉及到的闭包,更是令许多的初学者困惑不已。

    在之前的章节中有介绍过,定义函数的方法有两种:一种是函数声明。

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

    对于函数声明,有一个重要的特征就是可以把函数声明放在调用它的语句后面,因为解释器会在执行语句之前先读取函数的声明。

    另一种方法是使用函数表达式。

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

    函数表达式与函数声明的一个主要不同就是,它必须在定义之后才能使用,否则将会报错。函数表达式中的函数是一个匿名函数,即function关键字后面没有标识符。既然可以把函数赋值给变量,也就可以把函数作为其它函数的返回值。

    递归

    JavaScript中的递归有个问题,将保存函数的变量赋值给另一个变量时,因为函数名称改变了,所以在递归调用的时候会出现问题:

        function factorial(num){
            if (num <= 1){
                return 1;
            } else {
                return num * factorial(num-1);
            }
        }
    
        var anotherFactorial = factorial;
        factorial = null;
        alert(anotherFactorial(4)); //出错!
    

    要解决这个问题,可以使用arguments.callee。这个属性是一个指向正在执行的函数的指针,所以可以利用它来代替函数名,这就确保递归调用时函数名称改变也不会出错。但是,在严格模式下,使用argments.callee会导致错误。我们可以使用命名函数来达成相同的结果:

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

    闭包

    闭包是一个容易令人困惑的概念。闭包是指有权访问另一个函数作用域中的变量的函数。创建装饰的常见方式就是嵌套定义函数。

    我们在第4章的时候了解过作用域链。而对于作用域链清晰地理解,是理解闭包的重要关键。

    当调用函数的时候,会为函数创建一个执行环境,并将该环境的活动对象加入到作用域链的前端。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

    当在一个函数内部定义另一个函数的时候,外部函数的活动对象会被添加到内部函数的作用域链中。一般情况下,当函数执行完毕后,它的活动对象就会被销毁。但是,当有内部函数与其构成闭包时,因为内部函数的作用域链仍在引用外部函数的活动对象,因此只有当内部的函数也被销毁后,这个外部活动对象才会被销毁。

    闭包与变量

    闭包只能取得包含函数(即外部函数)中任何变量的最后一个值。

        function createFunctions(){
            var result = new Array();
            for (var i=0; i < 10; i++){
                result[i] = function(){
                    return i;
                };
            }
            return result;
        }
    

    这个函数返回的函数数组中的每个函数都会返回10。因为每个函数的作用域链中都保存着createFunctions()的活动对象,所以它们引用的都是同一个变量i。当createFunctions()返回后,i的值是10,所以每个函数引用保存的变量i都是10。可以使用另一个匿名函数来修正这个问题:

        function createFunctions(){
            var result = new Array();
            for (var i=0; i < 10; i++){
                result[i] = function(num){
                    return function(){
                        return num;
                    };
                }(i);
            }
            return result;
        }
    

    关于this对象

    this对象是在运行时,基于函数的执行环境绑定的。但是,匿名函数的执行环境具有全局性,因此其this对象通常指向window

        var name = "The Window";
        var object = {
            name : "My Object",
            getNameFunc : function(){
                return function(){
                    return this.name;
                };
            }
        };
        alert(object.getNameFunc()()); //"The Window"(在非严格模式下)
    

    这是因为每个函数都有自己的this,所以当内部函数在搜索这个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问到外部函数中的this

    模仿块级作用域

    之前讲过,在JavaScript当中没有块级作用域。但是,我们可以使用匿名函数来模仿块级作用域。

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

    在函数的声明包含在一对圆括号中,表示它实际上是一个函数表达式,而紧随其后的另一对圆括号会立即调用这个函数。

    在需要用到块级作用域的时候,就可以这样使用:

        function outputNumbers(count){
            (function () {
                for (var i=0; i < count; i++){
                    alert(i);
                }
            })();
    
            alert(i); //导致一个错误!
        }
    

    私有变量

    从技术角度来讲,JavsScript中是没有私有成员的概念的,所有对象属性都是公开的。因但是对于函数而言,函数里面的变量对外部都是私有的,我们可以利用在函数里面创建一个闭包,来创建用于访问函数内部私有变量的公有方法。

    对于有权访问私有变量和私有函数的公有方法,我们称之为特权方法(privileged method)。创建特权方法的方式有两种:一是在构造函数当中定义特权方法。

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

    对于这个例子而言,变量privateVariable和函数privateFunction()只能通过特权方法publicMethod()来访问,我们无法直接访问到内部私有的变量。
    使用模式的缺点是针对每个实例都会创建同样一组新方法,可以使用另一种方法,静态私有变量来避免这个问题。

    静态私有变量

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

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

    这个模式使用了函数表达式来定义特权方法,因为函数声明只能创建局部的函数,同样地,对于MyObject我们也没有使用var关键字,这样可以使其成为一个全局变量。

    这个模式与构造函数模式的主要区别在于,私有变量和函数是由实例共享的。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法作为一个装饰,问题保存着对包含作用域的引用。

    模块模式

    模块模式是为单例创建私有变量和特权的方法。所谓单例,即只有一个实例的对象。

    一般情况下,JavaScript是以对象字面量的方式来创建单例对象的。

    模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:

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

    如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。

    增强的模块模式

    这个模式即在返回对象之前加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性或方法对其加以增加的情况。

        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;
        }();
    

    小结

    这章主要讨论了JavaScript当中的函数表达式与闭包。理解闭包的一个重要基础就是要透彻理解执行环境和作用域链。

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

  • 相关阅读:
    java操作生成jar包 和写入jar包
    jboss配置jndi连接池
    windows 域的LDAP查询相关举例
    LDAP error Code 及解决方法
    HDU 6417
    CF1299D Around the World
    codechef Chef and The Colored Grid
    Educational Codeforces Round 82 (Rated for Div. 2)
    CF1237F Balanced Domino Placements
    CF1254E Send Tree to Charlie
  • 原文地址:https://www.cnblogs.com/buginux/p/4123521.html
Copyright © 2011-2022 走看看