用函數處理函數,來擴充函數原本的功能

 Thu, 23 Sep 2010 14:25:04 +0800

最近在看nodejs討論群組,有人問怎樣可以讓函數只執行一次。後來想出了一個簡單的Once()函數,可以「處理」令一個函數讓他只執行一次。接下來又想到好幾個可以這樣子擴充別的函數功能的函數,所以紀錄一下。

其實做法也很簡單,就是在原本的函數外面用匿名函數包裝,讓原本的函數在適當的條件下被執行。我想到了幾個用的到的功能:

以後還想到什麼東西的話,還可以再來擴充,先把目前想到的記錄下來。程式如下:
var Sugars = {
  'Once': function(f){
    var active = false;
    return function(){
      var args = [];
      for(var i=0; i<arguments.length; i++){
        args.push(arguments[i]);
      }
      if(active) return;
      active = true;
      return f.apply(null, args);
    };
  },
  'Times': function(f,n){
    var count = n;
    return function(){
      var args = [];
      for(var i=0; i<arguments.length; i++){
        args.push(arguments[i]);
      }
      if(count>0){
        count--;
        return f.apply(null, args);
      }
    };
  },
  'Later': function(f,cb) {
    return function() {
      var args = [];
      for(var i=0; i<arguments.length; i++){
        args.push(arguments[i]);
      }
      setTimeout(function(){
        if(cb) return cb(f.apply(null, args));
        return f.apply(null, args);
      },50);
    };
  },
  'Curry': function(f) {
    var orig_arg_num = f.length,
    args = [],
    inner = function() {
      for(var i=0; i<arguments.length; i++) {
        args.push(arguments[i]);
      }
      if(args.length<f.length) {
        return inner;
      }
      if(args.length>=f.length) {
        return f.apply(this, args);
      }
    };
    if(arguments.length>1) {
      for(var i=1; i<arguments.length; i++) {
        args.push(arguments[i]);
      }
    }
    return inner;
  },
  'Recurser': function(f) {
    return function(x) {
      return f(function() {
        var args = [];
        for(var i=0; i<arguments.length; i++) {
          args.push(arguments[i]);
        }
        return x(x).apply(null, args);
      });
    }(function(x) {
      return f(function() {
        var args = [];
        for(var i=0; i<arguments.length; i++) {
          args.push(arguments[i]);
        }
        return x(x).apply(null, args);
      });
    });
  }
};

除了Recurser之外,使用上應該都不困難。大致說明一下:

以下是自己寫的簡單測試:

var f = function(){
  return "executed.";
};
var t1a = Sugars.Once(f);
try {
  alert(t1a());
  alert(t1a());
}catch(e){alert(e);}
var t2a = Sugars.Times(f, 2);
try {
  alert(t2a());
  alert(t2a());
  alert(t2a());
}catch(e){alert(e);}
var t3a = Sugars.Later(f, function(m){alert(m);});
try {
  t3a();
}catch(e){alert(e);}
var f1 = function(a,b,c,d,e) {
  return e-d+c-b+a;
};
var t4a = Sugars.Curry(f1, 1, 2, 3);
try {
  alert(t4a(4)(5));
}catch(e){alert(e);}
var f3 = function(g) {
  return function(n){
    if(n==0) {
      return 0;
    } else if(n==1) {
      return 1;
    } else if(n>1) {
      return g(n-1) + g(n-2);
    }
  };
};
var t5a = Sugars.Recurser(f3);
try {
  alert(t5a);
  alert(t5a(5));
}catch(e){alert(e);}

Sugars.Recurser()的問題還很多,建議還不要測試,因為這樣比直接寫遞迴還麻煩,哈哈。因為需要把函數currying化,第一次接受的參數其實是函數自己,第二次接受的參數才是函數需要的參數。Y-Combinator有點複雜,我還沒改出可以傳給他非currying形式的函數。不過這個東西應該沒有什麼實際用處。

Sugars.Curry()在你用currying的方式呼叫,參數個數超過函數定義的參數個數時,就會出問題。另外,也不能接受不定參數的函數,因為需要用參數個數當作一個終止條件。也許以後再想比較好的方法來做。