Keep on moving

あんまりまとまってないことを書きますよ

若手IT勉強会に参加してきました。(4章クロージャ①)

主催のid:cimadaiさんお疲れ様でした。

Secrets of the Javascript Ninja

Secrets of the Javascript Ninja


今回はNinjaの4章クロージャでした。私の方で解説をしたのですが、思った以上に理解できてないことを痛感したのでまとめ直しておきます。クロージャは内容が多いので、何回かに分けてまとめます。説明をすると自分の勉強にもなることを再確認しました。

クロージャ

クロージャとは、関数から外部の変数にアクセスして操作することをいいます。
WikiPediaの解説はクロージャ - Wikipediaにあります。これを見た感じだと、クロージャの元祖はSchemeみたいですね。
実際に変数がどのスコープで扱われるかは下の実行例を参照して下さい。

クロージャでprivate変数

JavaScriptにはいわゆるprivate変数はないですが、クロージャを使うことで同じようなことが実現できます。

function Ninja(){
  var slices = 0;

  this.getSlices = function(){
    return slices;
  };
  this.slice = function(){
    slices++;
  };
}

関数コンテキストの実行

JavaScriptでよくはまる"this"についての説明です。
例えば以下の様な場合、Button内での"this"は、イベント内で扱われる場合、"test"の"this"が使われてしまいます。↓を参照してください。

ここでprototypeで使われているbindを導入してみます。

function bind(context, name){
  return function(){
    // ここでのargumentsはreturnされたfunctionのもの
    // 例) elem.addEventListener("click", bind(Button, "click"), false);
    //     elem.click("hoge","fuga");
    // とした場合、arguments=["hoge","fuga"]となる。
    return context[name].apply(context, arguments);
  }
};

prototypeでのorginal版は以下の様になっています。

Function.prototype.bind = function(){
  // 本当はarguments.slice()としたいのだが、argumentsはArray風オブジェクトのため、
  // argumentsをコンテキストにしてArray.prototype.sliceで実行する。
  var fn = this, args = Array.prototype.slice.call(arguments),
    object = args.shift();

  return function(){ return fn.apply(object,
    args.concat(Array.prototype.slice.call(arguments)));
  };
};

Ffの場合、callとapplyで処理時間の違いはほとんどない

var using_call = function(){

// callのargumentsが第一引数にあたる。第2引数指定なしの場合は 0扱い
// i.e. arguments.call(0)と同じ

args = Array.prototype.slice.call(arguments);
var sum=0;
for(var i=0;l=args.length,i<l;i++){
  sum += args[i];
}
return sum;}

var using_apply = function(){
args = Array.prototype.slice.apply(arguments);
var sum=0;
for(var i=0;l=args.length,i<l;i++){
  sum += args[i];
}
return sum;}

var loop=100000;

console.time("call");
for(var i=0;i<loop;i++)using_call(1,2,3,4,5);
console.timeEnd("call");
// -> 1508ms

console.time("apply");
for(var i=0;i<loop;i++)using_apply(1,2,3,4,5);
console.timeEnd("apply");
// -> 1514ms

Ffでは結果はほとんど変わりません。

Withでもクロージャは作成可能

JavaScript でブロックスコープを実現する: Days on the Moon

var f=[];
console.time("aaa");
for (var i=0; i<100000; i++) {
  with({i:i}){
   f[i] = function(a){console.log( a+i)};
  }
}
console.timeEnd("aaa");
f[3](4);