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当中的函数表达式与闭包。理解闭包的一个重要基础就是要透彻理解执行环境和作用域链。

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

  • 相关阅读:
    andrid 上传图片 asp.net 后台接收并保存
    Volley封装
    error: Error retrieving parent for item: No resource found that matches the given name 'android:Widget.Material.ActionButton'.
    The type android.support.v4.view.ScrollingView cannot be resolved. It is indirectly referenced from
    Recyclerview 实现上拉加载更多
    RecyclerAdapter封装
    项目中自己一直用到的baseAdapter的类
    SwipeRefreshLayout 和RecyclerView 使用
    DrawerLayout 使用
    学习动态性能表 v$sql
  • 原文地址:https://www.cnblogs.com/buginux/p/4123521.html
Copyright © 2011-2022 走看看