既存の関数の呼び出しをフックする方法
既存の関数の呼び出しの前後にあれこれしたいというのはよくある話ですが、割りと簡単にできる方法です。
簡単な例
//オリジナルの関数
function add(n, m){
return n + m;
}
//呼び出してみる
add(1, 2) // 3
//フックしてみる
var origin = add;
add = function(){
console.log(arguments);//呼び出されたパラメータを出力
//オリジナルへ委譲
var result = origin.apply(null, arguments);
//結果を2倍して返す
return result * 2;
}
add(1, 2) // 6
consoleへのフック
応用として、console.logの出力を本番と開発でオンオフするということをやってみます。
console.log、console.info、console.error、console.warnをフックします。
//開発フラグがtrueの場合は出力を行う、本番モードはfalseにする
console.development = false;
//関数名を分解
"log,warn,error.info".split(",").forEach(function(key){
//オリジナルの関数を取得
var origin = console[key];
//consoleに上書き
console[key] = function(){
//デバッグフラグがあるときだけオリジナルへ委譲
if(console.development){
//Node.jsならconsoleは不要、ブラウザは必要
origin.apply(console, arguments);
}
};
});
上記例で言えば、単純にif(development)console.log = function(){}
でよいのですが、
実例としてconsole.logが実行された時に、本番モードではリングバッファに追記して、
レポート出力時にバッファから出力するという目的でフックを使っています。
インスタンスの関数へのフック
prototypeを使えばStringの関数のフックも可能です。
var origin = String.prototype.charAt;
String.prototype.charAt = function(index){
console.log("index=" + index);
return origin.call(this, index);
};
インスタンスの関数をフックするときにはcallやapplyの第一引数にはthisを渡す必要があります。
引数がハッキリしている場合はapplyではなくcallで呼び出せます。
使いみちが少なそうな感じもしますが、私はNode.jsのrequireの呼び出しをフックするために使っています。
//モジュールからモジュールクラスを取得
var Module = module.constructor;
//プロトタイプのrequireを取得
var origin = Module.prototype.require;
//requireを置き換え
Module.prototype.require = function(path){
//何がrequireされたのかを出力
console.log(path);
//オリジナルへ委譲
return origin.call(this, path);
}
//何かをrequireしてみる
require(any);//ログが出力される
制限
この方法はシンプルですが、コンストラクタには適用できません。