js之函数表达式与闭包

232 阅读4分钟
递归

递归函数是指一个函数在自身内部调用本身.

  • 当通过名字调用本身时,又将函数索引赋给另外一个变量,将自身置为null时,调用该函数会报错
    function sum(a){
      if(a==1){
        return 1;
      }else{
        return a*sum(a-1);
      }
    }
    console.log("不改变函数索引指向时没问题:"+sum(4));
    var test=sum;
    sum=null;
    console.log("改变了函数索引:"+test(24));
    //打印结果
    不改变函数索引指向时没问题:24
    e:\vue\vue-demo\src\view\test.js:5
        return a*sum(a-1);
                 ^

    TypeError: sum is not a function
  • 非严格模式下可以使用arguments.callee()方法避免上述问题
    function sum(a){
      if(a==1){
        return 1;
      }else{
        return a*arguments.callee(a-1);
      }
    }
    var test=sum;
    sum=null;
    console.log("改变了函数索引依旧没问题:"+test(4));
    //打印结果
    改变了函数索引依旧没问题:24
  • 以函数表达式结合函数声明的方式定义不论是严格模式还是非严格模式
    var factorical=(function f(num){
      if(num<=1){
        return 1;
      }else{
        return num*f(num-1);
      }
    })
    var cur=factorical;
    factorical=null;
    f=null;
    console.log(cur(4));
    //打印结果
    24
闭包
  • 匿名函数:function(){},既没有函数名,也没有赋给变量.
    function sayName(){
        var name="zhangsan";
        return function(){
            return name;
        }
    }
    console.log(sayName()())
    //打印结果
    zhangsan
  • 闭包:是指有权访问一个函数作用域的函数 ---->函数里面定义函数

要想理解闭包这个概念,首先要清楚执行环境作用域链变量对象.

  1. 后台的个执行环境都有一个表示变量的对象---->变量对象

  2. 全局环境的变量对象始终存在,而像函数这样的局部环境的变量对象,只在函数执行过程中存在。

  3. 创建并调用函数的作用域链的过程

  4. 变量会沿着作用域链往找. 具体实例分析和说明:

  function createCompareFunction(propName){
    return function(object1,object2){
        var value1=object1[propName];
        var value2=object2[propName];
        if(value1>value2){
            return 1;
        }else if(value2==value1){
            return 0;
        }else{
            return -1;
        }
    }
  }
  var ob1={
      name:"zhangsan",
      age:24
  };
  var ob2={
      name:"lisi",
      age:25
  };
  //创建函数
  var compareAge=createCompareFunction("age");
  //调用函数
  var result=compareAge(ob1,ob2);
  console.log(result);
    //打印结果
    -1

上述示例的执行环境的作用域链图:

闭包和变量
  • 闭包只能取得包含函数中任何变量的最后一个值,这是闭包的副作用----->闭包一般是函数套着函数,包含函数是指包含闭包函数的函数
    function createFunctions(){
         var result=new Array();
         for(var i=0;i<10;i++){
             result[i]=function(){
                 return i;
             }
         }
         return result;
     }
     // result是一个函数数组
     var results=createFunctions();
     console.log("i的最终值是10");
     console.log("调用result数组里任意一个函数获得:"+results[0]());
     //打印结果
     i的最终值是10
     调用result数组里任意一个函数获得:10

注意:根据作用域链,如果results[0]函数里找不到i的值,会在createFunctions()查找i的值,发现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;
   }
   // result是一个函数数组
   //每个function()的作用域链上一层都有一个function(num)活动对象,而num的值不同
   var results=createFunctions();
   console.log("调用results[0]:"+results[0]());
   console.log("调用results[1]:"+results[1]());
   //打印结果
   调用results[0]:0
   调用results[1]:1
this 对象
  • 当函数作为某个对象的方法调用时,this等于那个对象.
  • 匿名对象的执行环境具有全局性this指向window对象.
  • 每个函数在被调用时都会自动取得两个特殊变量:this arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数的这两个变量.
   //内部函数是指function(),外部函数是指getNameFunc()
    var object={
        name:"my object",
        getNameFunc: function(){
            return function(){
                return this.name;
            }
        }
    }
    console.log(object.getNameFunc()());
    //打印结果
    undefined

注意: function()getNameFunc()找不到this对象,令that=this即可

    var object={
        name:"my object",
        getNameFunc: function(){
            var that=this;
            return function(){
                return that.name;
            }
        }
    }
    console.log(object.getNameFunc()());
    //打印结果
    my object
内存泄漏
  • 如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁.
    function assignHandler(){
        var element=document.getElementById("someElement");
        element.onclick=function(){
            console.log(element.id);
        }
    }

解决方式:

    function assignHandler(){
        var element=document.getElementById("someElement");
        var id=element.id;
        element.onclick=function(){
            console.log(id);
        }
        element=null;
    }
模仿块级作用域
  • JavaScript块级作用域可以访问块级作用域变量
    function assignHandler(){
         for(var i=0;i<10;i++){
             var j=3;
         }
         console.log("i的最终值:"+i);
         console.log("j的值:"+j);
    }
    assignHandler();
    //打印结果
    i的最终值:10
    j的值:3

js多次声明同一个变量,会对后面的声明视而不见,不过依旧会按照后续的变量声明初始化.(指在同一个函数中)

  • 函数里定义的变量是局部变量,在函数外不能访问,可通过匿名函数模拟块级作用域------->(function(){})(),匿名函数必须用括号括起来再调用。
    function assignHandler(){
        (function(){
            for(var i=0;i<4;i++){
                console.log(i);
            }
        })();
        console.log("无法访问,此时i的值为:"+i);
    }
    assignHandler();
    //打印结果
    0
    1
    2
    3
    ReferenceError: i is not defined
私有变量
  • 函数里面定义的变量是私有变量,可通过特权方法来访问这些私有变量。
  • 特权方法:在函数部定义一个闭包,而闭包可以通过作用域链来访问函数的私有变量
    function Person(name){
        this.getName=function(){
            return name;
        };
        this.setName=function(value){
            name=value;
        };
    }
    var person=new Person("zhangsan");
    console.log(person.getName());
    person.setName("lisi");
    console.log(person.getName());
    //打印结果
    zhangsan
    lisi

不直接用this.name=name,因为这样定义它的实例就可以访问了。

静态私有变量
  • java中静态变量变量,所有实例可共享,很容易让我们想起js的对象原型
  • 私有变量可通过闭包的方式定义.
  • 那么静态私有变量,可结合两者定义.
  (function(){
      var privateVariable=10;
      function privateFunction(){
          return false;
      };
      //没用 var定义,则是全局变量
      MyObject=function(){};
      MyObject.prototype.publicMethod=function(){
          privateVariable++;
          return privateFunction();
      };
  })();
  var ob1=new MyObject();
  console.log(ob1.publicMethod());
  var ob2=new MyObject();
  console.log(ob2.publicMethod());
  //打印结果
  false
  false
  

对象设置真正的私有或者静态私有变量,不直接把变量与对象绑定,而是闭包(也就是方法)获取或者设置变量。

示例2:

    (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("zhangsan");
    console.log(person1.getName());
    var person2=new Person("zhangdsan");
    person2.setName("lisi");
    console.log(person2.getName());
    //打印结果
    zhangsan
    lisi
模块模式
  • 模块模式是为单例创建私有变量特权方法
  • 单例在js中是通过字面量的方式定义的.
  var person={name:"zhangsan"}
  • 模块模式通过为单例添加私有变量和特权方法能够使其得到增强.
  var singleton=function(){
      var privateVariable=10;
      function privateFunction(){
          return false;
      }
      //返回一个对象
      return {
          publicProperty: true,
          publicmethod: function(){
              privateVariable++;
              return privateFunction();
          }
      }

  }();
  console.log(singleton.publicmethod());
  //打印结果
  false

示例2:留出一个方法(接口)注册组件

    var application=function(){
        var components=new Array();
        components.push(new BaseComponent());
        //返回对象来操作私有变量 components
        return {
            getComponentCount:function(){
                return components.length;
            },
            registerComponent:function(component){
                if(typeof component =="object"){
                    components.push(component);
                }
            }
        };
    }();
    application.registerComponent(component1);

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

增强的模块模式
  • 与原本返回任意对象(return {})不同,增强的模块模式是对某种类型的实例进行增强
    var application=function(){
        var components=new Array();
        components.push(new BaseComponent());
        // BaseComponent为一种对象类型
        var app=new BaseComponent();
        app.getComponentCount=function(){
            return components.length;
        };
        app.registerComponent=function(component){
            if(typeof component =="object"){
                components.push(component);
            }
        };
        return app;
    }();
    application.registerComponent(component1);