Js函数知识(2)

115 阅读3分钟

递归函数

  • 递归函数必须满足两个条件:

    1. 调用自身
    2. 有终止条件
  • 作为普通命名函数的递归

  • 作为对象方法的递归(匿名函数):可能导致引用丢失的问题

    var obj={
        chrip:function(n){
            return n>1?obj.chrip(n-1)+"-chrip":"chrip";
        }
    }
    obj.chrip(5);  //chrip-chrip-chrip-chrip-chrip
    
    var newObj={
        chrip:obj.chrip
    }
    newObj.chrip(5);  //chrip-chrip-chrip-chrip-chrip
    
    obj={};
    newObj.chrip(5); //undefined;  **此处obj.chrip丢失**
    

    解决引用丢失的方法:使用this或使用内联函数

    1. 使用this:要求obj和newObj的方法名称必须都为chrip

       var obj={
           chrip:function(n){
               return n>1?this.chrip(n-1)+"-chrip":"chrip";
           }
       };
      
    2. 参照以下内联函数

  • 作为内联函数的递归:给方法的匿名函数起一个名称.内联函数的函数名称只有在自身函数内部可见

     var obj={
         chrip:function sound(n){
             return n>1?sound(n-1)+"-chrip":"chrip";
         }
     }
     
     var newObj={
         chripSound: obj.chrip
     }
    

函数是第一型对象

因为函数为第一型对象,所以函数可以有自己的属性。用途:

  1. 函数存储:最常见的是事件回调管理.

    下例根据函数本身的属性来判断函数是否被存储

     var store={
         nextId:1,
         cache:{},
         add:function(fn){
             if(!fn.id){
                 fn.id=this.nextId++;
                 return !!(this.cache[fn.id]=fn);  // !!将表达式强制转换为等效布尔值
             }
         }
     };
     function fn(){};
     
     assert(store.add(fn),"fn is saved","fn is not saved"); //fn is saved
     assert(store.add(fn),"fn is saved","fn is not saved"); //fn is not saved
    
  2. 自记忆函数:通过函数属性来保存计算结果,提高性能。

    下例通过函数的answer属性来缓存计算结果

     function isPrime(value){
         if(!isPrime.answer) isPrime.answer={};
         if(isPrime.answer[value]!=null){
             return isPrime.answer[value];
         }
         
         var prime=value!=1;
         for(var i=2;i<value;i++){
             if(value%i==0){
                 prime=false;
                 break;
             }
         }
         
         return isPrime.answer[value]=prime;
     }
    
  3. 模拟数组:有些时候一个数据集合可能需要包含一些元数据属性。这里我们介绍将对象当作数组来操作。 当然我们也可以把数组添加属性或方法来处理这种情况。这里我们只演示把对象当数组。

     var elems={
         length:0,
         add:function(elem){
             Array.prototype.push.call(this,elem);//强制把elems当作数组
         },
         gather:function(id){
             this.add(document.getElementById(id));
         }
     }
    
     elems.gather("first");
     elems.gather("second");
     
     console.log(elems.length);//2,此处把elems对象当作数组来处理,其length属性是伪数组的数据长度
    

函数的重载

JS中没有强制函数声明多少参数就得传入多少参数。函数能否成功处理这些参数完全取决于函数本身。基于这一点,可以根据传入参数的不同对函数进行重载。
  1. 使用if-else类处理不同的语句_当逻辑复杂是,函数本身会显的非常笨拙

     function(){
         if(arguments.length==0){//do something}
         else if(arguments.length==1){//do something}
         else if(arguments.length==1){//do something}
         ..
     }
    
  2. 利用闭包的性质进行重载

     //Part1:定义重载的对象以及实现重载的方法
     var overloading={};
     function addMethod(object, name, fn){
         var old=object[name];
         object[name]= function(){
             if(fn.length===arguments.length){
                 return fn.apply(this,arguments);
             }
             else if(typeof old=='function'){
                 return old.apply(this, arguments);
             }
         }
     };
    
     //part2:对logging方法进行4次重载
     addMethod(overloading,'logging',function(){console.log('no argument');});
     addMethod(overloading,'logging',function(arg1){console.log('1 argument');});
     addMethod(overloading,'logging',function(arg1, arg2){console.log('2 arguments');});
     addMethod(overloading,'logging',function(arg1, arg2, arg3){console.log('3 arguments');});
     
     //part3:测试4个重载的方法
     overloading.logging();          //no arguments
     overloading.logging(1);         //1 argument
     overloading.logging(1,2);       //2 arguments
     overloading.logging(1,2,3)      //3 arguments
    

    上例中利用了闭包的特性,来实现对logging方法的重载。这样做的好处是每次重载都使用了单独的匿名函数,从而便于维护和理解。

    在Part1中,old和fn都利用了闭包,每次执行logging方法时,old和fn都是指向上一次保存的匿名函数。

    在part2中,第四次重载完成后,object[name]中的fn指向的是第四次重载是定义的匿名函数,而old则是指向了第三次重载时object[name]的值;以此类推。

    在Part3中,每次调用logging方法是,先是判断传入的参数是不是3个,如果不是再判断传入的参数是不是2个,以此类推。