• JavaScript学习 六、函数表达式


     前文说过定义函数的方式有两种,一种是函数声明、一种是函数表达式。两者最大的区别是函数声明提升,即函数的声明在执行代码前会先被读取。

    递归

     递归函数是在一个函数中通过名字调用自身的情况。前面我们讲过的一个计算乘阶的函数:

    function factorial(num){
        if(num <= 1){
            return 1;
        }else{
            return num*factorial(num - 1);
        }
    }
    
    console.log(factorial(5));    //120

    我们知道,函数名只是一个引用,所以也可以进行赋值,当factorial 被赋值为null或者其他函数引用时,就会发生错误,如下:

    var anotherFactorial = factorial;
    factorial = null;
    anotherFactorial(5);   //TypeError: factorial is not a function

    前文讲过,使用arguments.callee 可以解决问题。

    function factorial(num){
        if(num <= 1){
            return 1;
        }else{
            return num*arguments.callee(num - 1);
        }
    }
    
    console.log(factorial(5));    //120
    var anotherFactorial = factorial;
    factorial = null;
    console.log(anotherFactorial(5));   //120

    但是在严格模式下,使用arguments.callee 会导致错误。不过可以使用命名函数表达式来达到相同的效果。

    "use strict"
    var factorial = (function f(num){
        if(num <= 1){
            return 1;
        }else{
            return num*f(num - 1);
        }
    });
    
    console.log(factorial(5));    //120
    var anotherFactorial = factorial;
    factorial = null;
    console.log(anotherFactorial(5));   //120

    这种方式在严格模式和非严格模式下都能行得通。

    闭包

     闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

    function createComparisonFunction(propertyName){
        return function(object1, object2){
            var value1 = object1[propertyName];
            var value2 = object2[propertyName];
    
            if(value1 < value2){
                return -1;
            }else if(value1 > value2){
                return 1;
            }else{
                return 0;
            }
        };
    }
    var compareNames = createComparisonFunction("name");
    var obj1 = {
        name: "Lilei",
        age: 18
    };
    var obj2 = {
        name: "HanMeimei",
        age: 17
    };
    console.log(compareNames(obj1, obj2));
    compareNames = null;

    这里需要理解compareNames 函数的作用域链,函数的作用域链保存在内部的[[Scope]] 属性中,当函数被调用的时候就为函数创建一个执行环境,然后通过复制函数的 [[Scope]] 属性中的对象构建其执行环境的作用域链。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

    createComparisonFunction 函数作用域链包括自身的变量 和 全局变量,compareNames 函数的作用域链包括 自身变量 和 createComparisionFunction 的变量 和 全局变量。

    当createComparisonFunction 退出时,返回一个compareNames 函数,自身的作用域链销毁, 但是自身的活动对像由于被compareNames 引用,所以仍然会留在内存中,知道compareNames 函数被销毁( compareNames = null; )

    1.闭包与变量

    作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。

    function createFunctions(){
        var result = new Array();
        for(var i = 0; i < 10; i++){
            result[i] = function(){
                return i;
            };
        }
        return result;
    }
    
    var res = createFunctions();
    res.forEach(function(value, index, array){
        console.log(value.call());   // 10 10 10 ... 10 
    });

    函数返回十个函数组成的数组,数组中的十个函数的作用域链都是自身和createFunctions 的对象,读取变量 i 值时,找到作用域链的 createFunctions 对象,在数组中的函数调用的时候,createFunctions 对象中的 i 已经变成了10, 所以返回的函数返回值都是 10。

    解决办法是再创建一个匿名函数,在作用域链中隔离开自身和 createFunctions 的对象。

    function createFunctions(){
        var result = new Array();
        for(var i = 0; i < 10; i++){
            result[i] = function(num){
                return function(){
                    return num;    
                };
            }(i);
        }
        return result;
    }
    
    var res = createFunctions();
    res.forEach(function(value, index, array){
        console.log(value.call());   // 0 1 2 ... 9
    });

    本例中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给了属猪。这里的匿名函数有一个参数 num, 也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量 i 。 由于函数参数是按值传递的,所以就会将变量i的当前值复制给 num。而在这个匿名函数内部,有创建并返回了一个访问num 的闭包。这样一来,result 数组中每一个函数都有自己 num 变量的一个副本,因此就可以返回各自不同的数值了。

    2.闭包的this对象

    在闭包中使用 this 翠系那个也可能会导致一些问题。匿名函数的执行环境具有全局性,因此其this对象通常指向 global。

    var global = function(){
        return this;
    }();
    global.name = "The global";
    
    var object = {
        name: "My Object",
        getNameFunc: function(){
            return function(){
                return this.name;
            };
        }
    }
    console.log(object.getNameFunc()());  //The global

    前面说过,每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。 内部函数在搜索这两个变量时,只会搜索到其活动对象位置,因此永远不可能直接访问外部函数中的者两个变量。

    模仿块级作用域

     如前所述,JavaScript 中没有块级作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。即使后面重新声明变量,也于事无补。

    for(var i = 0; i<10; i++){
    
    }
    var i;
    console.log(i);  //10

    匿名函数可以解决这个问题,用作块级作用域(通常被称为私有作用域)的匿名函数的语法如下:

    (function(){
        //todo code 块级作用域
    })();

    以上代码定义并立即调用了一个匿名函数。将函数声明包含在一对全括号中,表示它实际上是一个函数表达式。而金穗气候的另一对圆括号会立即调用这个函数。

    (function(){
        for(var i =0; i<10; i++){
        }
    })();
    console.log(i);  //ReferenceError: i is not defined

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

    私有变量

     任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量 和 在函数内部定义的其他函数。

    我们把有权访问私有变量和私有函数的共有方法称为特权方法(privileged method)。

    有两种在对象上创建特权方法的方式:

    第一种:在构造函数中定义特权方法。

    function MyObject(){
           //私有变量
        var privateVar = 10;
            //私有方法
        function privateFunc(){
            return false;
        }
            //特权方法
        this.publicMethod = function(){
            privateVar++;
            return privateFunc();
        }
    }

     定义特却方法有一个缺点,就是你必须使用构造函数模式来达到这个目的。构造函数构建对象的缺点是对每个实例都会创建同样一组方法。

    第二种:静态私有变量

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

    这个模式创建了一个私有作用域,兵在其中封装了一个构造函数及相应的方法。在私有作用域中首先定义了私有变量和私有函数,然后又定义了构造函数和公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数时没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那不是我们想要的。出于同样的原因,我们也没有在声明MyObject 时使用var 关键字。记住:初始化未经声明的变量,总是会创建一个全局变量。因此MyObject就i成了一个全局变量,能够在私有作用域之外被访问到。但是在严格模式下,给未经声明的变量赋值会导致错误。

    多查找作用域链中的一个层次,就会在一定程度上影响查找的速度。这正是使用闭包和私有变量的一个明显的不足之处。

    模块模式

     前面的模式用于为自定义类型创建私有的变量和特权方法。而道格拉斯所说的模块模式则是为单利创建私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。按照惯例,JavaScript是以对象字面量的方式来创建单例对象的。

    var singleton = {
        name: value,
        method: function(){
        
        }
    };

    模块模式通过为单例添加私有变量和特权方法能够使其得到增强。

    var singleton = function(){
        var privateVar = 10;
        function privateFunc(){
            return false;
        }
        return {
            publicProperty:  true,
            publicMethod: function(){
                privateVar++;
                return privateFunc();
            }
        };
    }();

    这个模块模式使用了一个返回对象的匿名函数。在这个匿名函数的内部,首先定义了私有变量和函数。然后,将一个对象字面量作为函数的值返回。返回的对象字面量中质保含可以公开的属性和方法。

     增强的模块模式

    改进模块模式,就是在返回对象之前加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例,同事还必须添加某些属性和(或)方法对其甲乙增强的情况。

    var singleton = function(){
        var privateVar = 10;
        function privateFunc(){
            return false;
        }
        //创建对象
        var object = new CustomType();
        object.publicProperty = true;
    
        object.publicMethod = function(){
            privateVariable++;
            return privateFunction();
        };
        return object;
        
    }();

    小结

     在JavaScript 编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无需对函数命名,从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用JavaScript 函数的强大方式。

    • 函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫匿名函数。
    • 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂。
    • 地柜函数应该始终使用 arguments.callee 来递归调用自身,不要使用函数名----函数名可能会发生变化。

    当在函数内部定义了其他函数是,就创建了闭包。闭包有权访问包含函数内部的所有变量。

    • 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域 和全局作用域。
    • 通常,函数的作用域及其所有变量都会在函数执行结束后销毁。
    • 但是,当函数返回了一个闭包是,这个函数的作用域将会一直在内存中保存到闭包不存在为止。

    使用闭包可以在JavaScript 中模仿块级作用域(JavaScript中本省没有块级作用域的概念)。

    • 创建并立即调用一个函数,这样既可以执行其中的代码,有不会在内存中留下对该函数的引用。
    • 结果就是函数内部的所有变量都会被立即销毁----除非将某些变量赋值给了包含作用域(即外部作用域)中的变量。

    闭包还可以用户与在对象中创建私有变量。

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

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

      

  • 相关阅读:
    一台计算机安装两个版本的MySQL
    用php实现显示上个月的最后一天
    SQL 如何去掉字段中千位的逗号(比如set @= '1,320.00' 想得到@= '1320.00' )
    jsp表单提交中的逻辑判断
    将两个字段中的值合并到一个字段中
    vue判断开始日期不能大于截至日期
    mySql中The user specified as a definer ('root'@'%') does not exist
    mysql GROUP_CONCAT给每个值加上单引号后再拼接
    javascript如何获取复选框中的值?
    mybatis中的useGeneratedKeys="true"
  • 原文地址:https://www.cnblogs.com/liangflying521/p/5133481.html
Copyright © 2020-2023  润新知