2009年1月アーカイブ

1ヶ月前までSubversionのことを「スーパービジョン」と読み間違えていた私ですが、ようやく更新・コミット以外の便利機能を使えるようになってきました。

SVN、Subversionを理解するためのカギ

SVNというシステムは、次の3者で成り立っている。
  • ユーザ
  • SVNクライアント(=TortoiseSVN)
  • レポジトリを保持しているSVNサーバ

この概念を理解したとたん、すっと霧が晴れたような感じがしました。
それからは、新しい機能を覚えたり、マニュアルを読んで理解したりできるようになりました。

SVN初心者が覚える恐怖

SVNシステムを使いはじめた当初、私の脳裏には次の2者しかいませんでした。

  • ローカルPCのファイル・フォルダ群
  • サーバのレポジトリ

そして、
  • コミット = アップロード
  • 更新 = ダウンロード
という認識でした。

ソースコードをFTPでアップするのと同じようなイメージ。
社内LANの共有フォルダに保存するのと同じようなイメージ。
ただし、バックアップをこまめに取ってくれて、いつでも好きな時点のバージョンに戻せるという便利機能がついている、そんな認識でした。

コミット時によく見かける下記の用語がさっぱり理解できませんでした。
  • 追加
  • 紛失
  • 無視
  • 管理外

「紛失」「管理外」をあまりにも頻繁に見かけるので、だんだんストレスを感じるようになりました。 TortoiseSVNが嫌いになりました。

他にも意味不明なメッセージが私を苦しめます。
  • クリーンアップしてください。
  • ロックされています。
  • コミットできません
  • 入れ子
などなど。

エクスプローラのアイコンに現れるあの変な色のアイコンを見るの苦痛でした。
Overlays.png 赤いびっくりマークがどうやっても消えません。
本当にSVNというのは、初心者に優しくないシステムだと思いました。


しかしあるときふと思いました。

「紛失とか管理外とか言うのは、誰にとっての紛失、誰にとっての管理外なんだろう?」

そう考えたとき、SVNクライアント(=TortoiseSVN)の存在が浮かび上がってきました。

私は気がつきました。

バージョン管理システムには3つの層がある

1つめは、ローカルPC上で自分がみているファイル・フォルダ群。
2つめは、SVNクライアント(=TortoiseSVN)が認識しているファイル・フォルダ群
3つめが、離れたSVNサーバマシン上のあるファイル・フォルダ群(=レポジトリ)

1つめの層と2つめの層は、常に内容が同じというわけではない。
ユーザがファイルを作ったり削除したり編集したりすると、1つめの層と2つめの層に「ズレ」が生じる。
このズレが、「管理外」とか「紛失」とか呼ばれるもの。

「紛失」というのは、1つめの層でファイル・フォルダが削除されたことが、2つめの層に伝わっていない状態。
「管理外」というのは、1つめの層でファイル・フォルダ作られたことが、2つめの層に伝わっていない状態。

つまり、「紛失」「管理外」とは、2つめの層が1つめの層を観察して、自己と比較したときの差異のこと。

そしてこの差異を埋める作業が、
「右クリック→ TortoiseSVN → 追加」であり、
「右クリック→ TortoiseSVN → 削除」である。
この作業を怠ると、2番目の層が「管理外」「紛失」とうるさく騒ぎ立てる。


また、2つめの層と3つめの層も、内容が同じでないときがある。
この差異を埋める作業が、 「右クリック → SVN 更新」であり、 「右クリック → SVN コミット」である。 そして、「無視リストに追加」されたファイル・フォルダ群は、1層目と2層目にとどまり、3層目には伝わらない。


まとめ

1層目と2層目の違いを意識することが、初心者がSubversionシステムを理解する第一歩である x
TortoiseSVNを使っていて、ファイルやフォルダを削除するときは要注意です。

ファイルやフォルダを普通に削除してはいけません。

ファイルやフォルダ削除するときは必ず、

右クリック > TortoiseSVN > 削除

で削除しましょう。
普通にファイルやフォルダを(deleteキーなどで)削除すると、「紛失」というステータスになります。

05_funshitu.JPG
削除したのに「紛失」とはなんだ!!と
私は最初戸惑いを感じました。

削除した私本人にとっては「紛失」ではないからです。

では一体誰にとって「紛失」なのか??
たぶんイメージ的には、ローカルPCで働いているTortoiseちゃんから見たときの「紛失」なんでしょう。

ものすごくわかりやすく書くとこんな感じなんでしょう。

Tortoiseちゃん:
「ふぁー。ご主人様(PCユーザ)から呼び出されたから、今日も仕事すっか。
 なになに、コミットをしたいのですね。
 準備しますので少々お待ちを。
 あれ、ふと気づいたら昨日まであったファイル・フォルダがなくなってる!!
 聞いてないよ!!
 紛失だ紛失だ!!」

「紛失」はトラブルのもと

「紛失」は、トラブルの元になります。
「紛失」のままコミットをすると、コミットできなくなることがあります。
特にフォルダの紛失は痛いです。
TortoiseSVN コンテキストメニューを使用しないで、フォルダをエクスプローラで削除した場合、作業コピーを破損してしまいコミットできなくなります。
作業コピーを更新すると、Subversion はリポジトリから最新のフォルダを取得し、紛失したフォルダと置き換えます。
そのため、フォルダを削除する正しい方法は、TortoiseSVN → 削除 を使用することです。
(TortoiseSVNヘルプ)

フォルダを削除するときは必ず

右クリック > TortoiseSVN > 削除

です。
これ鉄則!!

イメージ的にはこんな感じです。

ユーザ:「Tortoiseちゃん、このフォルダを消しといてね。よろしく」
Tortoiseちゃん:「はい、かしこまりましたご主人さま。
  次回のコミットの際に、サーバちゃんにレポジトリから削除するように言っておきます。」

うっかりdeleteキーで削除してしまったら

もし間違えてdeleteキーなどでファイル・フォルダを削除してしまった場合は、

 右クリック > SVN更新

で復元してくれます。

まだコミットしていない場合


右クリック > TortoiseSVN > 追加を元に戻す


02.JPG

既にコミットしてしまっている場合


シフトキー押しながら右クリック > TortoiszeSVN > 削除 ローカルファイルを保持

これをやると、レポジトリからは削除されて、自分のローカルには残ります。


01.JPG
みんながSVNを使ってるから自分もSVNを入れたけど使い方がよくわからん。
更新とコミットはできるけど、それ以外やったことない。
機能いっぱいありすぎて全然意味分からん。
マージとかDiffとかブランチとか意味分からん。

1週間前までの自分はこんな感じでした。

同じ症状の人は、とりあえず日本語HTMLヘルプをダウンロードしましょう。
結構わかりやすいです。
結構便利です。

Subversionによるバージョン管理 (Subversion Book) の翻訳
日本語のHTMLヘルプがダウンロードできます。

注意点:
普通にZIPを解凍してヘルプを開くとエラーが出ます。

ファイルを右クリック→「ブロックを解除」
で見れるようになります。
WEBいろいろ調べるより、このヘルプファイルを見た方がよっぽど便利です。
ぜひ使ってみてください。

Smartyのforeachは大変便利ですが、from=$list で渡した配列を全要素ループしてしまいます。

全部ではなく、例えば$listの要素のうちの5個分だけループしたいときは、"index"プロパティを使うとできました。

{foreach from=$list item=var name=myloop}
{if $smarty.foreach.myloop.index < 5 }
{$var}
{/if}
{/foreach}

前回の記事ですが、オリジナルはJavaで書かれています。
Java版とPHP版を比較してみると、コード記述にほとんど差がない。
むしろJavaの方が若干コード量が少ない気がする。
ショック!

Java版

http://www.objectclub.jp/technicaldoc/testing/stack_tdd.pdfより引用
import java.util.EmptyStackException;
/**
* @author koji,hiranabe
*/
public class Stack {

    private int[] value = new int[10];
    private int size;

    public boolean isEmpty() {
        return size == 0;
    }
    public int top() {
        emptyCheck();
        return value[size - 1];
    }
    public void push(int value) {
        this.value[size++] = value;
    }
    public int size() {
        return size;
    }
    public void pop() {
        emptyCheck();
        --size;
    }
    private void emptyCheck() {
        if (isEmpty())
        throw new EmptyStackException();
    }
}

PHP版

<?php
class Stack {

    protected $value = array();
    protected $size = 0;

    public function isEmpty() {
        return $this->size == 0;
    }
    public function top()  {
        $this->emptyCheck();
        return $this->value[$this->size - 1];
    }
    public function push($x)  {
        $this->value[$this->size ++ ] = $x;
    }
    public function size()  {
        return $this->size;
    }
    public function pop()  {
        $this->emptyCheck();
        $this->size--;
    }
    private function emptyCheck()  {
        if($this->isEmpty()){
            throw new OutOfRangeException();
        }   
    }
}
?>
Ligthweight Languageとは名ばかりの。。。
PHPのOOPはもはやLLではないのか(涙

[PHP] limeでTDDを体験する

PerlのTest::Moreに相当するものが、PHPではlime.phpというやつである。

Kwappa開発室-TDDを体験する という記事で、PHPUnitを使ってTDD体験という話が大変面白かった。
私も体験してみようと思ったが、limeしか使えないのでlimeでTDD体験をしてみた。

準備

lime.phpを入手して、WEBサーバ上の任意のディレクトリに以下のように配置する。
  • lime.php
  • Stack.class.php (空ファイル)
  • TestStack.php (空ファイル)
これから「スタック」という機能を持つクラスを作ります。
仕様はリンク先を参考のこと。

なお、私は諸事情により、コマンドラインではなくブラウザ上で実行しました。

Stack.class.php
クラスを定義する。
<?php

class Stack {

}

?>
TestStack.php
他の2つのクラスを読み込む。
<?php

require_once("lime.php");
require_once("Stack.class.php");

$stack = new Stack();
$t = new lime_test();

?>

TDDをする

まずテストをかく。
TestStack.php
<?php

require_once("lime.php");
require_once("Stack.class.php");

$stack = new Stack();
$t = new lime_test();

echo("<pre>");  // 出力を整形する

$t->diag('Stack Test');  // テストの宣言
$t->ok($stack->isEmpty(), 'isEmpty');  // これが1個目のテスト

echo("</pre>");

?>

そして、失敗するように実装をかく。
Stack.class.php
<?php

class Stack {
    public function isEmpty()
    {
        return false;
    }

}

?>

StackTest.phpを実行すると、見事に失敗する。

以下略
(リンク先のブログ記事をご参照くだされ)

最終的に書いたコード

最終的にはこのようなコードになりました。
テストコード
TestStack.php
<?php
require_once("lime.php");
require_once("Stack.class.php");
echo(1);

$stack = new Stack();
$t = new lime_test();

echo("<pre>");
$t->diag('Stack Test');

try{
    $stack->pop();
    $t->fail('Exception not thrown when void pop');
}catch(OutOfRangeException $e){
}

try{
    $stack->top();
    $t->fail('Exception not thrown when void top');
}catch(OutOfRangeException $e){
}

$t->ok($stack->isEmpty(), 'isEmpty');

$stack->push(1);
$t->ok(!$stack->isEmpty(), 'NOT isEmpty');

$t->is($stack->top(),1,'top');
$t->is($stack->size(),1,'size');
$stack->push(2);
$t->is($stack->size(),2,'size');
$t->is($stack->top(),2,'top');

$stack->push(0);
$t->is($stack->top(),0,'top');
$stack->pop();

$stack->pop();
$t->is($stack->top(),1,'top');
$stack->pop();
$t->is($stack->size(),0,'pop');

$t->ok($stack->isEmpty(), 'isEmpty');

try{
    $stack->pop();
    $t->fail('Exception not thrown when void pop');
}catch(OutOfRangeException $e){
}

try{
    $stack->top();
    $t->fail('Exception not thrown when void top');
}catch(OutOfRangeException $e){
}

echo("</pre>");

?>
テスト対象のクラスコード
Stack.class.php
<?php

class Stack {

    protected $value = array();
    protected $size = 0;

    public function isEmpty()
    {
        return $this->size == 0;
    }
    
    public function push($x)
    {
        $this->value[$this->size ++ ] = $x;
    }
    
    public function top()
    {
        $this->emptyCheck();
        return $this->value[$this->size - 1];
    }

    public function size()
    {
        //return 0;
        return $this->size;
    }
    
    public function pop()
    {
        $this->emptyCheck();
        $this->size--;
    }
    
    private function emptyCheck()
    {
        if($this->isEmpty()){
            throw new OutOfRangeException();
        }
    
    }
}

?>
テスト結果
# Stack Test
ok 1 - isEmpty
ok 2 - NOT isEmpty
ok 3 - top
ok 4 - size
ok 5 - size
ok 6 - top
ok 7 - top
ok 8 - top
ok 9 - pop
ok 10 - isEmpty
1..10
 Looks like everything went fine.

ほ~勉強になった。
やっぱ体験は重要ですな。

プチ楽しかったです。
みなさんもぜひ。
小数点を切捨てるには、Math.floor()を使います。

<script>
var a = Math.floor(2.9999);
alert(a);  // '2'と表示
</script>

サイ本 第3章 データ型と値

3種類の基本データ型

JavaScriptには、基本データ型が3種類ある。

データ型値の例
数値3
文字列'hello'
論理値true,false

また、nullとundefinedという特殊なデータ型がある。
これらは値が1種類しかない。

データ型値の例
nullnull
undefinedundefined

複合データ型

オブジェクトと呼ばれる複合データ型がある。
オブジェクトには、配列と関数も含まれる。
逆に、配列と関数は、特殊な振る舞いをするオブジェクトである。

Date,RegExp,Errorなどもオブジェクトである(型ではない)。

[JavaScript] 予約語の一覧

サイ本 第2章2.8 予約語

ECMAScript v3で決められている予約語の一覧です。
意外と少ないんですね。


break
case
cache
continue
default
delete
do
else
false
finally
for
function
if
in
instanceof
new
null
return
switch
this
throw
true
try
typeof
var
void
while
with


ifやfunctionはよく使います。
逆に使ったことがないのは、default,delete,finally,instanceof, voidあたり。
今後、意識して使うにしようっと。

使用頻度分析とかしたら面白いかも。
達人プログラマと初心者プログラマの違いがはっきり出るんだろうな。ww


サイ本 第2章2.6 識別子

識別子というのは変数や関数につける名前のことです。

$記号を好きな位置で使えるところが特異ですね。
var $ =   1;
var $1 =  2;
var $x =  3
var x$ =  4;
var $$ =  5;
var $$$ = 6;
var $_  = 7;
var _$  = 8;

alert($);
alert($1);
alert($x);
alert(x$);
alert($$);
alert($$$);
alert($_);
alert(_$);

$は、jQueryやprototype.jsで使われていますね。

いま気づいたんですが、$_も普通に使えるんですね。
Perlっぽくてよさげです。


サイ本 第2章2.5 コメント

Java,C,C++と同じだそうです。
// 行コメント

/*  コメント */

/* 
 複数行に
 わたる
 コメント
 */

/* ~ */ はネストできない

ネストするとエラーになります。

/* 

/*  */ ← エラー!!

 */
ほ~知らんかった。


括弧の読み方に自信がなかったので、調べてみました。
プログラムでよく使われる4つに限ってまとめました。

「アレ」とか呼んだりしちゃっていませんか?
自己流の呼び方をしている人は、すぐ悔い改めましょう。(w


種類日本語名1日本語名2英語名1英語名2
( )丸括弧(まるかっこ)小括弧(しょうかっこ)ParenthesesRound brackets
{ }波括弧(なみかっこ)中括弧(ちゅうかっこ)BracesCurly brackets
[ ]角括弧(かくかっこ)大括弧(だいかっこ)Box bracketsSquare brackets
〈 〉山括弧(やまかっこ) Angle brackets

出展:
Wikipedia - 括弧
Wikipedia - Bracket

英語名の方は何回見ても覚えられない。。。。><
長年、アレをなんと読むのか知りませんでした。

[ ] ← コレ

「角括弧」というのは知ってましたが、読み方がわからない。

かくかっこ?かどかっこ?つのかっこ?

正解は、「かくかっこ」

「かくかっこ」「かくがっこ」が正解のようです。
大辞林

ほー。長年の謎が解けた。

他にも、大括弧(だいかっこ)ブラケットという言い方もあるそうです。
Wikipedia - 括弧

JavaScript第5版  第2章2.4セミコロン

文末のセミコロンは必須ではない。
a = 3
b = 4
alert(a)
alert(b)
しかし、セミコロンは入れるように推奨されている。
理由は、JSパーサが勝手にセミコロンを補うので、予期せぬ結果になることがあるため。

悪い例
function x(){
  return
  true
}
alert(x());  // 'undefined'
これは、
return;
true;
と解釈されてしまったため。


サイ本を熟読して基本をおさらい中。

サイ本 第2章2.2 大文字と小文字


JavaScriptでは、大文字と小文字は区別される。

alert(1);
Alert(1); // エラー
a = 1;
A = 2;
alert(a);  // 1
alert(A);  //  2
a = function(){alert(1);}
A = function(){alert(2);}
a(); // 1
A(); // 2

WSH/Jscriptでは?

基本的には上記と同じふるまい。
ちょっと変わってるのが、WSHオブジェクトのメソッドでは大文字小文字をどちらでも呼び出せる。
WSH.echo(1);
WSH.ECHO(1);
WSH.Echo(1);
WSH.EchO(1);
WSH.eCHo(1);
WSH.Quit();
WSH.QUIT();
WSH.quiT();

コレ全部OK。
何故だかはわからない。また、どういう条件の場合これができるのかも不明。

このサイトあのサイトこのサイトなんかで、デュアルモニターの快適性に書かれていたのを読んで、自分もいつかはとタイミングをねらっていました。
丁度、ずっと使ってきた三菱の17インチモニター(2001年末に6万円ほどで購入)がだんだんガタがきて、表示も暗いし色もときどき変になるしで、先月思い切って買い換えました。

デルの19インチワイドモニター(SE198WFP)を2つ。合わせて4万円。
注文確定をクリックするのに勇気が要りましたが、設備投資じゃーと自分に言い聞かせて注文クリック。
これが12/11ごろのこと。

さすがはDELLで、わずか3日後に家に届きました。
早速梱包を解いてセッティングと。
何も考えずにディスプレー2台をデスクトップPCにつなげようとしたら、アレ?
PC側には出力端子がオンボードのやつ1つしかない。

うぐぅ。

グラフィックカードを買わないといけないのね。。
すぐさまヤフオクで1500円のDVI出力ロープロファイルビデオカードを購入し、PCに装着。
よっしゃこれでデュアルディスプレイや!とディスプレイつないでみたら、何故かオンボードでつないだ方のディスプレイに映像が出ない。
この時点でPCにはマザーボードオンボードのDSUB出力端子が1個と、後から装着したグラボのDVI出力端子が1個あるわけですが、私はこれでデュアルディスプレイができると思い込んでいたわけです。
ところが、グラボとディスプレイをつないだ瞬間に、オンボードの方が表示されなくなる。

ちょっと調べてみたら、たいていのマザーボードはこういう仕様になっていて、グラボから映像出力するとオンボードの方が自動的に無効になるそうですね。

うぐぐぅ。。。

しょーがない。気を取り直して、カカクコムでデュアル出力対応のロープロファイルグラフィックカードを買う。玄人志向のGFX5200-LA18Cというやつ。4500円なり。
(結局、先ほど購入したグラボはお蔵入りにorz )

発送でかなり待たされて、やっと届いた新グラボをさっそく装着。
よく見たら片方がDVIで片方がDSUBなんですね。ちょっと残念。

気を取り直して接続。
ちゃんと2画面に映像が表示されました。
おーっと感動しようとしたが、よく見るとDVIから出力した方の映像がちょっとおかしい。
気持ち映像が横長に伸びて見えるし、文字が横につぶれてかすんで見える。DOS窓で字が読みにくい。
ウィドウを最大化すると横がちょっとだけ画面からはみ出す。

なんじゃこりゃ?
どう見てもモニタの解像度と出力映像の解像度があっていない。

しかしデスクトップのプロパティを見ても、ちゃんと解像度1440x900に設定されている。

うごごぉぐぅあおお~~~~

DELLのモニタが悪いのか、グラボのハードウェアの故障か、ドライバの不具合か、さっぱりわからん。。。いったい誰に文句を言えばいいのか?

とりあえず抱えているWEBサイト開発案件が手一杯だったので、不具合解決はいったんあきらめて、片方のディスプレイの映像がかすんだままプログラム開発を続行。
perlの文字化け問題(0x5C問題)と格闘しながら年が暮れる。

年が明けてちょっとスケジュールに余裕が出来たので、改めてこの「かすれ」問題に取り組む。

まず、DELLの不良かどうかを確かめるために、2台のディスプレイのDVIとDSUBを逆にしてみる。
お、今度もDVIでつないだ側がかすれて表示される。DSUBでつないだらきれいに表示される。
ということはモニタの以上ではないな。

次に、ドライバを疑ってみる。
天下のnVIDIAに限って、ドライバの不具合などないだろうとタカをくくっていたのだが、nVIDIAのサイトから最新のドライバをインストールしたらあっさり解決した。

あ、ドライバの不具合だったの。
なんてあっけない結末。

というわけで、かれこれ1ヵ月の格闘の末、やっとデュアルディスプレー環境が構築できました。

こんな感じです。
dual_monitor.JPG

めちゃめちゃ快適です。
作業効率が数十%向上するというのもあながち嘘ではないかも。

(ちょっと横の広さを持て余し気味ですがw)


というわけでみなさん、デュアルディスプレーにするときは、一筋縄でいかないことがあるのでよく調べてから環境構築をしましょう。


function funcname(args){ do something};

は

var funcname = function(args){ do something};

と等価になる。
javascriptを理解するためのたった2つの大切なこと
javascriptを理解するためのたった2つの大切なこと:改

引用元の記事はすばらしい記事ですが、ここは間違いです。
等価だと思っていたらひどい目にあいます。
サイ本 第5版のp.96にちゃんと書いてあります。
function文はプログラムの静的な構造を定義するだけなのです。
JavaScriptコードが解析されコンパイルされたときに、関数は定義されます。

どういうことかというと、
function foo( ){ ... }
コンパイル時に関数が定義されます。

var foo = function( ){ ... };
代入文の実行時に関数が定義されます。

その証拠に、
foo();
function foo(){ alert(1) }
はOKですが、

foo(); // エラー
var foo = function (){ alert(1) };
はエラーになります。

ささいな違いに見えますが、プロトタイプを使うときは要注意です。
プロトタイプを使ったメソッド定義は必ず代入文だからです。

メソッド定義は代入文!!

FireFoxでは下記のようなメソッド定義をしようとすると、何故かエラーになります。(IEでは動きます)
function Foo(){  }; // クラス宣言
function Foo.prototype.greet(){ alert('hi') }; // エラー
従って、メソッド定義は代入文で書くことになります。
function Foo(){  }; // クラス宣言
Foo.prototype.greet = function (){ alert('hi') }; // メソッド定義は代入文で

正常に動作するケース

よって、下記のコードは動きます。
function Foo(){  };
Foo.prototype.greet = function (){ alert('hi') };

var foo = new Foo();
foo.greet();

これも動きます。
Foo.prototype.greet = function (){ alert('hi') };
function Foo(){  };

var foo = new Foo();
foo.greet();

こんなのもOKです。
Foo.prototype.greet = function (){ alert('hi') };

var foo = new Foo();
foo.greet();

function Foo(){  };

こんなんだって動いちゃいます。
var foo = new Foo();

Foo.prototype.greet = function (){ alert('hi') };

foo.greet();

function Foo(){  };

エラーになるケース

これはエラーです。
var foo = new Foo();
foo.greet();

function Foo(){  };
Foo.prototype.greet = function (){ alert('hi') };

これもエラーです。
var foo = new Foo();
foo.greet();

Foo.prototype.greet = function (){ alert('hi') };
function Foo(){  };
おわかりでしょうか?

まとめ

  • function foo ( ){..} と var foo = function ( ) {..} は、関数が生成されるタイミングが違う
  • プロトタイプによるメソッドの宣言は、代入文である。
  • メソッドの代入は、
     クラス宣言をした後、かつ、インスタンスからメソッド呼び出しをする前
    にやるべし。

クロージャとは

クロージャは、言葉で説明するのが大変難しい概念です。

あなたは、自転車の乗り方を、身振り手振りなしで口だけで説明できるでしょうか?
あるいは螺旋(らせん)の形を、文章で説明できるでしょうか?

きっと難しいと思うでしょう。

しかし、自転車に乗ることは誰でもできますし、針金1本あれば、螺旋(らせん)の形を作ることはできるでしょう。

「クロージャ」もこれと同じです。
だから、Wikipediaのこんな解説を見ても落ち込まないでください。
クロージャ (クロージャー、Closure) は、プログラミング言語において引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決する関数のことである。
理解できないですよね?
私もそうでした。
クロージャを既に知っている人にしか、この文章は理解できないでしょう。

クロージャを作るのは難しくない

しかし、説明するのは難しくても、作るのは意外と簡単。それがクロージャです。
手元にウェブブラザさえあれば、誰でもクロージャは作れます。
準備はいいですか?
これから一緒にクロージャを作りましょう!

目次

参考になったページ


クロージャを理解する手がかりになったページ


クロージャを理解した後で読むと目からウロコなページ

追記①
はてなブックマークでたくさんブックマークいただいたので、さらにわかりやすくなるように書き直しました。
「ここが分かりにくい」などありましたら、コメントいただければ幸いです。

ちなみに「螺旋」を文章で説明すると、
「回転しながら回転面に垂直成分のある方向へ上昇する曲線」(Wikipeidaより)
だそうです。www

(2009.12.4)
追記②
Perlのクロージャ入門書きました。
[Perl]猿でもわかるクロージャ超入門
今回がクロージャ超入門の最終回です。(涙)

クロージャは、理解するよりも作るほうが簡単

前回、「関数を返す関数」をいじって、いきなりクロージャを作ってしまいました。
こんなやつです。
<script>
//サンプル5-1

function outer(){
    var x = 1;	 // outerのスコープ内で変数を定義

    return function (){ //この関数が「クロージャ」
        alert(x);  // "関数内関数"の中で、outerスコープの変数を参照。
    };

}

var f =  outer(); 
f();  // 1と表示。
</script>
これが何故クロージャなのかというと、次の条件を満たしているからです。
  • outerのスコープ内で変数を定義し、
  • outerの中に関数(=関数内関数)を作って
  • その関数内関数から、先ほどの変数を参照する

何の役に立つのか?

「クロージャが作れたのはいいけど、何がうれしいのか?」
もっともな疑問です。
では、先ほどのをちょっと改造したこんなコードを実行してみてください。
<script>
//サンプル5-2

function outer(){
    var x = 1;

    return function (){
        alert(x);
	x = x + 1;
    };

}

var f =  outer(); 
f();  // 1
f();  // 2
f();  // 3
</script>

あれ?

あれれ?

例の問題が解けちゃいましたね。
問題
 呼び出すたびに、1,2,3,...を返すような関数f()を定義せよ。
f();  //  1 
f();  //  2
f();  //  3
上のコードでは、1回目のf( )が呼び出された後、変数xの値(=2)が捨てられずに保持されます。
そこで2回目のf( )を呼び出すと、先ほどのxの値(=2)を覚えていて、それをalert表示してから、xの値を1増やして3にして、それをまた保持ます。

クロージャを使うと、このように「状態を保持する関数」を作ることができます。
「クロージャはオブジェクトに似ている」というのが実感できたかと思います。

これで、あなたはクロージャを100%理解しました。
最後まで読んでくれてありがとう。
おつかれさまですた。



補足1 無名関数は必須ではない。

サンプル5-2のコードは、他にもいろいろな書き方があります。
入門3回目で紹介した「無名関数」を使わなくても、クロージャは作れます。
(しかし実際にはクロージャと一緒に使われることが多いので紹介しました。)
「無名関数」を使わないクロージャ
//サンプル5-3

function outer(){
    var x = 1;

    function inner(){
        alert(x);
	x = x + 1;
    };

    return inner;
}

var f =  outer(); 
f();  // 1
f();  // 2
f();  // 3

補足2 「クロージャ」(Closure)という名前について

英和辞典で調べたら「閉鎖」という意味があるそうです。
変数xが、outer関数のスコープ内に閉じ込められて(closed)いて、inner関数以外のどこからも参照できないことに由来するのでしょう(たぶん)。
Javascriptでは、「関数を返す関数」というものを作れます。
<script>
//サンプル4-1

function outer(){
    var inner = function (){  // 無名関数を定義してinnerに代入
        alert("hello");
    }

  return inner;             // inner関数を返す
}

var f =  outer();             // outre関数は戻り値としてinner関数を返す。それがfに代入される。
f();                          // "hello"と表示。inner() が実行されたのと同じ効果がある。
</script>
outer関数が実行されると、
  1. outer内で無名関数が生成される
  2. それが変数innerに代入される
  3. そのinnerが戻り値として返される
  4. それがfに代入される
このとき、outer関数は
「関数を返す関数」
となっています。

「お母さんの胎内から赤ん坊が生まれて、"F"と名づけられ、"F"が"おぎゃあ"と泣いた」みたいな感じです。(変?)

同じことをやるのに、何通りかの書き方があります。

普通に関数を宣言してそれを返す
<script>
//サンプル4-2

function outer(){
    function inner(){  // inner関数を定義
        alert("hello");
    }

  return inner;      // inner関数を返す
}

var f =  outer();      // inner関数がfに代入される。
f();                   // "hello"と表示。
<・script>
function inner( )という風に関数innerを宣言しておきながら、return innerという風にinnerを変数のように扱っていて、実に気持ちが悪いですね。
innerは関数でもあり、変数でもあるのです。Javscriptにおける関数の2面性をよく表しています。

無名関数を定義してそのままreturnする
<script>
//サンプル4-3

function outer(){

    return function (){  // 無名関数を定義してすぐreturn
        alert("hello");
    };

}

var f =  outer();         // onter内で定義した無名関数がfに代入される。
f();                      // "hello"と表示。
<・script>
この例では、innerという文字が登場しません。
無名関数を使うとコードがとてもすっきりしますね。

さて、"hello"という文字列を事前に準備してみましょう。 上のサンプル4-3と本質的に何ら変わりないコードです。
<script>
//サンプル4-4

function outer(){

    return function (){
        var x = "hello";
        alert(x);
    };

}

var f =  outer();
f();  // "hello"と表示。
</script>

では次に、var x = "hello"の位置をちょっと外にずらしてみましょう。
<script>
//サンプル4-5

function outer(){
    var x = "hello";

    return function (){
        alert(x);
    };

}

var f =  outer(); 
f();  // "hello"と表示。
</script>
問題なく"hello"と表示されましたね。

実は、これがクロージャなんです。
クロージャデビュー、おめでとう!!\(^o^)/

これであなたはクロージャを90%理解したことになります。
あと1歩!


次の記事  猿でもわかるクロージャ超入門 5 クロージャを作る

Javascriptでは、関数を定義するのに2通りのやり方があります。(注1)

関数を定義する方法その1 (普通のやり方)
<script>
function speak(){ alert("hello"); }

speak(); // "hello"と出力
</script>
はい、普通ですね。

次はJS特有のやり方です。
関数を定義する方法その2 (無名関数を使う)
<script>
var speak = function (){ alert("hello"); }

speak(); // "hello"と出力
</script>
2行目に注目。
この式の右辺、function( ){...}の部分を無名関数といいます。

右辺で無名関数を生成して、それを変数speakに代入しています。
こうすると、変数speakはまるで関数のような振る舞いをします。(というか関数になります)
speak( )と括弧をつけてやることで、関数呼び出しを実現できます。

方法1と方法2は、ほぼ同じことをしていると考えて結構です。(注2)

無名関数を定義してすぐさま実行する

上記の方法2では、3つのステップを踏んでいます。
  • 無名関数を定義する
  • それを変数speakに代入する
  • speak( )で関数を実行する
わざわざ一時変数speakに代入しているところが無駄だと思いませんか?
実はこのステップは省けるんです。
<script>
(  function(){ alert("hello"); }  )(); // "hello"と出力
</script>
これで、一時変数speakを省くことができました。

このように、無名関数を定義してすぐさま実行したいときは
( function( ){..} )( );
という構文になります。
( function......... )( );
の左の括弧が何故必要なのか理解に苦しむところですが、これがないとエラーになります。
まあこういうものだと思ってください。(JSの次期バージョンでは不要になるらしいです)

このような無名関数の構文は、クロージャを実現するときによく使われます。
これでクロージャを60%ぐらい理解したことになります。
クロージャマスターまでだいぶ近づきました。(w


次の記事  猿でもわかるクロージャ超入門 4 関数を返す関数


注1:本当は2通り以上ある。
注2:厳密にはちょっとだけ違います。

クロージャとは関数である。

ずばり言います。
クロージャとは、関数である
まずココがポイント。

次に、たいていの場合、
クロージャとは関数の中の関数である
これで、クロージャのことを30%程度は理解したことになります。

だから、
「クロージャーって何?」
って誰かに聞かれたら、
「ああ、関数の中に書く関数のことでしょ」
って答えとけば、30%ぐらい正解ということになります。(w

関数の中の関数

Javascriptでは、関数の中に関数を書くことができます。
もうちょっと正確に言うと、関数の中で別の関数を定義することができます。
//関数の中で関数を定義

function outer(){
    function inner(){
        alert("hello");
    }
}
ここで、関数innerを呼び出すにはどうしたらいいでしょうか?
下記のコードをコピペして、デスクトップに.htmlファイルとして保存して、ブラウザで開いてみてください。
<script>
function outer(){
    function inner(){
        alert("hello");
    }
}

inner(); // エラー!
</script>
これはエラーになるか、もしくは何も表示されません。
innerはouter関数の中だけで通用するローカルな名前であって、 グローバル空間でinnerは定義されていないからです。
ではやり方を変えてみましょう。
<script>
function outer(){
    function inner(){
        alert("hello");
    }
}

outer(); // 何も起こらない!
</script>
これは、エラーにはなりませんが、やはり何も表示されません。
outer( )は実行されたものの、outer内のinnerは宣言されただけで実行(呼び出し)されていないからです。

innerを実行するには、outer内でinner( )を明示的に実行してやる必要があります。
<script>
function outer(){
    function inner(){
        alert("hello");
    }
    inner();  //←コレ
}

outer(); // "hello"と表示された!
</script>
おめでとうございます。
見事"hello"が表示されましたね。

outer( )が呼び出されたとき、outer内でまずinnerが宣言され、その直後にinner( )が実行されたのです。

これで、あなたはクロージャを30%程度理解したことになります。

「関数の中の関数」をもうちょっと進化させると、クロージャを作ることができます。
でももうちょっとだけ辛抱してください。
その前に、あと2つ別のテクニックを覚える必要があります。

次の記事  猿でもわかるクロージャ超入門 3 無名関数

問題です。

問題: 呼び出すたびに、1,2,3,...を返すような関数 f( )を定義せよ。
f();  //  1 
f();  //  2
f();  //  3

この問題、解けますでしょうか?

普通の関数では、できないと思います。
しかし「クロージャ」というのを使えば、このようなことができます。

クロージャって何だ?

「クロージャ」という言葉を、プログラムの本やサイトで目にすることがありますよね。

私が最初に見たのは続・初めてのPerl 改訂版(アルパカ本)でした。
まったく理解できませんでした。

その後、404 Blog not foundnaoyaさんのブログなどで「クロージャ」という単語を目にしました。
やはり、まったく分かりませんでした。

とどめの一撃はWikipediaの解説記事。

クロージャ (クロージャー、Closure) は、プログラミング言語において引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決する関数のことである。

本当にありがとうございました(涙)
私はあきらめました。

そして歳月は流れ・・・

最近、Javascriptで勉強してみたら、突然理解できるようになりました。
理解した、というよりも「クロージャが作れてしまった」のです。
上に書いた問題が解けてしまったのです。

「よくわかんねーけど、おれ、自転車に乗れてるぞ?」という、少年のような気持ちでした。

そこで、
「クロージャ」がまったく意味不明だった私がクロージャを作れるようになった過程
を、ここに披露いたします。(注)

私がクロージャを理解するために格闘して、わかったことは、

クロージャは理解するより作るほうが簡単。
クロージャを作るためには、次の3つのことだけできればよい。
ということでした。

クロージャを作成するために必要な3つのこと

  • 関数内の関数
  • 関数を返す関数
  • 無名関数
これらをしっかり理解すれば、クロージャを作って、理解することができます。

クロージャを作ってからクロージャを理解する。
理解するよりも、作る方が簡単。
これがポイントです。

次の記事 猿でもわかるクロージャ超入門 2 関数の中の関数

まとめ記事 [JavaScript] 猿でもわかるクロージャ超入門 まとめ






注1:理解できるようにはなったが、活用できてない自分がいるのは内緒です。

概要

任意のフォルダの、サブフォルダのサブフォルダ(サブサブフォルダ?)をリスト化して出力します。

ダウンロード

getSubSubFolders.js

使い方

スクリプトをc:¥getSubSubFolders.jsなどと保存して、
コマンドプロンプトで、
c:¥>cscript  getSubSubFolders.js //nologo
と入力すればOK。

実行結果の例

C:¥>cscript getSubSubFolders.js c:¥perl //nologo
cpan    build
cpan    sources
eg      aspSamples
eg      cgi
eg      fork
eg      IEExamples
eg      PerlEx
eg      Windows Script Components
eg      Windows Script Host
html    bin
html    Components
html    faq
・・・
長いので以下略

スクリプトのソースコード

var fso = new ActiveXObject("Scripting.FileSystemObject");

var path = argv(0);
var root = fso.getFolder(path);
var subs = getSubFolders(root);
var subsubs=[];

foreach(subs, function(sub){ subsubs = subsubs.concat(getSubFolders(sub)); });
foreach(subsubs, function($_){ echo($_.ParentFolder.Name + "\t" + $_.Name); });

//サブフォルダ一覧を取得
function getSubFolders(oFolder) {
return collectionToArray(oFolder.SubFolders);
}

//コレクションを配列に変換
function collectionToArray(collection){
var objEnu = new Enumerator(collection);
var array = [];
for (; !objEnu.atEnd(); objEnu.moveNext() ){
array.push(objEnu.item());
}
return array;
}

function echo(str) { WSH.Echo(str); }

function argv(i){
if(WSH.Arguments.length == 0) echo('引数が指定されていません。');
return WSH.Arguments(i);
}

function foreach(array,func){ for(var i in array) func(array[i]); }


人気記事

このアーカイブについて

このページには、2009年1月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2008年12月です。

次のアーカイブは2009年2月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

最近の人気記事