zoukankan      html  css  js  c++  java
  • JavaScript高级程序设计 函数表达式

    函数表达式

    函数定义的两种方式:

    • 函数声明(函数声明提升,非标准name属性可访问给函数指定的名字)

              函数声明提升:执行代码前先读取函数声明

     function functionName(arg0, arg1, arg2){
         //函数体
     }
    • 函数表达式(name属性为空字符串,匿名函数)
     var functionName = function(arg0, arg1, arg2){
         //函数体
     };  //注意这个分号

    与if···else···语句结合使用只能用函数表达式(理解函数声明提升的关键--函数声明和函数表达式的区别)

    7.1 递归

     递归函数:一个函数通过名字调用自身

     递归函数调用容易出问题:这里用一个最简单的阶乘函数来表示

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

     使用下面的代码会让他出错:

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

    这里的anotherFactorial是factorial的一个副本,但factorial函数内仍在调用factorial函数,但此时的factorial函数已经被置空。所以这里会出现因为命名问题而产生的错误。

    解决方法:

    1. arguments.callee 指向正在执行函数的指针(避免直接写明函数名带来的问题)  但严格模式下不能通过脚本访问
    2. 命名函数表达式        
     var factorial = (function f(num){
         if(num <= 1){
             return 1;
         }else{  
             return num * f(num-1);
         }
     });

    这种方法在严格模式下也能使用

     7.2 闭包

    闭包:有权访问另一个函数作用域中变量的函数 (常见情况就是在一个函数内部创建另一个函数)

    之所以内部函数可以访问到外部函数的变量,是因为内部函数的作用域链中包含了外部函数的作用域    =>   函数第一次被调用的时候发生了什么? 

      当某个函数第一次被调用时,会创建一个执行环境和相应的作用域链,并把作用域链赋值给一个特殊的内部属性([[Scope]])。然后再用this,arguments和其他命名参数来初始化函数的活动对象。在作用域链中,外部函数的活动对象始终处于第二位。

      执行环境有一个与之关联的变量对象,环境中有权访问的所有变量和函数都在这个变量对象中。为了保证对这些变量对象的有序访问,出现了作用域链。作用域链从自身的环境的活动对象开始,一直链接到全局环境的活动对象。

      当一个函数创建时,会创建一个预先包含全局变量对象的作用域链保存在[[Scope]]属性中。在调用函数时,会为函数创建一个执行环境,通过复制[[Scope]]中的对象构建起执行环境的作用域链。之后每有一个活动对象被创建就被推入作用域链的前端。(是引用而不是变量对象本身)

      函数执行完毕,局部变量就会被销毁,内存中仅保留全局作用域。

    闭包的特殊性:对于闭包,内部函数被单独调用时,作用域链会包括外部函数的执行环境。所以外部函数执行结束后,其作用域链会被销毁,但其活动对象仍留在内存中。直到其内部函数被销毁后才会被销毁。

     //闭包
     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 result = compareNames({name: "Nicholas" }, {name: "Grey"});
     //删除对函数的引用,以释放内存
     compareNames = null;

    过度使用闭包会造成内存占用过多,慎重使用。

    7.2.1 闭包与变量

    作用域链配置机制的副作用:闭包只能取得包含函数中任何变量的最后一个值。

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

    第一种情况下,数组每一次都返回10。因为每个函数的作用域链都保存着外部函数的活动对象,所以引用的都是同一个变量i,外部函数返回后,变量i的值是10,所以每一个函数内部的i值都是10。

    第二种情况可以符合预期。这里没有直接把闭包赋值给数组,而是定义了一个匿名函数,把实时的i值赋给num。

    7.2.2 关于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,arguments(调用时会自动获取)时会到其活动对象为止,不可能访问外部函数中的这两个变量。

    解决方法:

     var name = "The Window";
     
     var object = {    
         name: "My Object",
     
         getNameFunc: function(){
             var that = this;
             return function(){
                 return that.name;
             };
         }
     };
     
     alert(object.getNameFunc()()):  //"My Object"

    7.2.3 内存泄漏

     如果闭包的作用域链中保存了一个HTML元素,那么该元素无法被销毁。

     function assignHandler(){
         var element = document.getElementById("someElement");
         element.onclick = function(){
             alert(element.id);
         };
     }

    由于在匿名函数中调用了包含函数的活动变量element,所以只要匿名函数还存在,element的引用数至少是1(参考垃圾清理机制),内存永远不会被回收。

    解决方法:

     function assignHandler(){
         var element = document.getElementById("someElement");
         var id = element.id;    
         element.onclick = function(){
             alert(id);
         };
         element = null;
     }

     在闭包中删除了对HTML元素的循环引用(id)。但还要element变量置为null,因为闭包会引用整个包含函数的活动变量,包括element(即使不直接引用)。

    7.3 模仿块级作用域

    JavaScript没有块级作用域的概念(比如for循环中的i,在for循环之外也可以被访问到)。对已声明的变量重复声明会被忽略,但初始化会改变值。

    解决方法:通过匿名函数来模拟块级作用域

    (function(){
        //块级作用域
    })():

    实际上是一个函数表达式(不能去掉function外层的圆括号,因为JavaScript将function关键字当作一个函数声明的开始,然而函数声明后是不可以跟圆括号的,加这层圆括号可以将函数声明转换为函数表达式)

    作用:临时需要一些变量,限制向全局作用域添加过多的变量和函数。

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

    7.4 私有变量

    私有变量:在函数中定义的变量(不能在函数外部访问)

    创建访问私有变量的公有方法:通过闭包(可以通过自己的作用域链访问这些私有变量)

    特权方法:有权访问私有变量和私有函数的公有方法

    在对象上创建特权方法的方式:构造函数中定义特权方法

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

    除了使用publicMethod()之外无法直接访问私有变量。

    还可以利用这个特性来隐藏那些不应该被直接修改的数据

    7.4.1 静态私有变量

    在私有作用域中定义变量或函数,从而创建特权方法。

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

    公有方法是在原型上定义的(原型模式),使用的函数表达式而不是函数声明(函数声明只能创建局部函数)

    与构造函数法的区别是,私有变量和函数是由实例共享的(在原型上定义的)。

    这种情况下,私有变量就成为了静态的、所有实例共享的。不足之处在于,多查找作用域链的一个层次就会在一定程度上影响查找速度

    7.4.2 模块模式

    模块模式:为单例创建私有变量和特权方法(单例:只有一个实例的对象)  =>  用对象字面量的方法来创建

    使用返回对象的匿名函数的方法。可以应用在需要对单例进行某些初始化,但是又要维护其私有变量的时候。

    7.4.3 增强的模块模式

    单例必须是某种类型的实例,必须添加某些属性和方法对其增强的情况。

           

  • 相关阅读:
    android.os.NetworkOnMainThreadException 异常处理
    java.lang.SecurityException: Permission denied (missing INTERNET permission?) 解决
    javah 生成header file 报错 问题解决
    AAPT: libpng error: Not a PNG file 问题解决
    Tomcat启动报错 Failed to start component [StandardServer[8005]]解决
    Hibernate
    Linux常用命令总结
    模拟奇数乱码请求问题
    http国际化模拟请求
    db2数据库安装注意几个问题
  • 原文地址:https://www.cnblogs.com/hermionepeng/p/13048791.html
Copyright © 2011-2022 走看看