2010年5月15日土曜日

クロージャの解説

カメです。
さっそくですが、前回の NG コードを元に解説を行います。


var DynamicWrapper = function(_targetObj, _wrapperObj){
var wrapped = eval("(" + _targetObj.toSource() + ")"); // deep copy
for(var memberName in _targetObj){
if(typeof(_targetObj[memberName]) == "function"){
wrapped[memberName] = function(){
_wrapperObj.before(memberName);
var result = _targetObj[memberName].apply(_targetObj, arguments);
_wrapperObj.after(memberName);
return result;
};
}
}
return wrapped;
};


NG コードの流れは言葉で書くと以下のようになるでしょう。
  1. 引数として受け取った "_targetObj" のコピーを作成する
  2. "_targetObj" の要素ごとにループを開始する
  3. "_targetObj" の要素が "function" の場合以下の処理を行う
    • "_targetObj" のコピーに"_targetObj" の要素名で、ラップされた関数を定義する
  4. "_targetObj" の要素がなくなるまで、ループを継続する
  5. "_targetObj" のコピーを呼び出しもとに返す

でも実際には、落とし穴があるのです。
"_targetObj" のコピーに"_targetObj" の要素名でラップされた関数を定義する
コードでは以下の部分


wrapped[memberName] = function(){
_wrapperObj.before(memberName);
var result = _targetObj[memberName].apply(_targetObj, arguments);
_wrapperObj.after(memberName);
return result;
};

上記のコードの "function()" は上位の "function()" 内に存在するため、上位 "function()" 内の環境による影響を受けます。
そのため、変数 "memberName" は 上位の "function()" 内で宣言されており、その影響を受けます。
よって、"wrapped[memberName]" の "memberName" はループ実行時の値ですが、
"function()" 内の "memberName" は関数呼び出し時の "memberName" が使用されるのです。
# 関数呼び出し時の "memberName" は、今回ではループに使用された最後の値
そのため、呼び出した関数と、呼ばれた関数に乖離が生じていたのです。

どうでしょう?

みなさんのコードでループ内でちょっとかわったことをした際に、予想と違う動きをされたことがあるかもしれません。
そのときは、今回のようなクロージャが影響しているかもしれませんよ?

0 件のコメント:

コメントを投稿