読者です 読者をやめる 読者になる 読者になる

Perlいつやるの?今でしょ!

ゆーすけべー先生のPerl入門書がKindle本で出ました。詳細はこちら

Perlについて語ろう
Perlについて語ろう
posted with amazlet at 13.03.17
和田裕介 (2013-03-13)

僕はRuby, PHP, JavaScriptしかまともに触った事が無かったのですが、
心の中からこんな声が聞こえました↓

はいぃぃぃぃ!ということで、Perl入門してみることにしましたです。ちなみにゆーすけべー先生のKindle本には、Perlの基礎文法〜Webアプリの作り方まで、結構広範囲についてサクッと書かれていたのですが、各章の「キリ」がよくて、つまみ食いしながら読み進めることができる感じ。なので自分はまず基礎文法だけやってみたです(細かいところはすっとばして、頻出しそうなところだけつまみ食い)。

※ 以下のコードはKindle本に出てくるサンプルコードとは全く異なり、自分で書いたコードですー
※ ちなみに自分はKindle持ってないので、iphoneのKindleアプリ使って読みますた

配列、ハッシュ

配列、ハッシュの扱い方は色々な方法があるようだけど、自分は以下のように無名リファレンスを使う方法が使いやすかった。javascriptの配列リテラルやオブジェクトリテラルに似ている!

use strict;                                                                                                                                                                                                 
use warnings;

my $users = [                                                                                                                                                                                            
  {id => '001', name => 'hoge', sex => 'man'},
  {id => '002', name => 'fuge', sex => 'woman'}
];

# 要素にアクセス
print $users->[0]->{name}; #hoge

# ループでまわす
foreach my $user (@$users) {
  print $user->{name}; #hogefuge
}

関数

javascriptのように無名関数を引数に渡して、コールバックできるんすね!引数の扱い方がちょっと独特だけど、そのうち慣れるでしょー

use strict;                                                                                                                                                                                                 
use warnings;

sub foo {
  my $callback = shift;
  my $y = 'yyy';

  $callback->($y);
}

# レキシカル変数
my $x = 'xxx';

# 無名関数を関数に渡す
foo(sub {
  my $yy = shift;

  print "$x\n"; # xxx
  print "$yy\n"; # yyy
});

cpan

cpanRubyのgemみたいなもんかな。Kindle本でも紹介されてたAcme::Oppaiというジョークモジュールを使ってみた。Acme::ってのはジョークモジュールにつける名前空間らしい。::で名前空間を繋げて行くあたりはRubyに似てる。ちなみにperlとcpanmの環境設定は、Kindle本の「一歩進んだPerlの環境つくり」という章に書いてあったー

$ cpanm Acme::Oppai
use strict;
use warnings;
use Acme::Oppai;

print Acme::Oppai->oppai;
#  _  ∩
#( ゜∀゜)彡 おっぱい!おっぱい!
# ⊂彡  

OOP

OOPの勉強がてらAcme::Oppaiの拡張的なものを作ってみた。コマンドラインからoppaiコマンドを叩くと、引数によって表示されるAAが変わる代物。

クラス概要


ディレクトリ構成
.
├── Acme/
│   ├── OppaiEx/
│   │   ├── KnightsOfRound.pm
│   │   ├── Kuma.pm
│   │   └── Manager.pm
│   └── OppaiEx.pm
└── oppai*
oppai

コマンドとなるプログラム。こいつに実行権限をつけて、パスを通す

#!/usr/bin/env perl                                                                                                                                                                                         

use strict;
use warnings;
use Acme::Oppai;
use Acme::OppaiEx;

my $key = @ARGV ? shift : '';

my $oppai;
if ($key eq "kuma") {
  $oppai = Acme::OppaiEx::Kuma->new();
} elsif ($key eq "knights") {
  $oppai = Acme::OppaiEx::KnightsOfRound->new();
} else {
  $oppai = Acme::Oppai->new();
}

my $manager = Acme::OppaiEx::Manager->new($oppai);
$manager->show;
Acme/OppaiEx.pm

各パッケージを読み込むプログラム

package Acme::OppaiEx; 

use strict;                                                                                                                                                                                                 
use warnings;
use Acme::OppaiEx::Manager;
use Acme::OppaiEx::Kuma;
use Acme::OppaiEx::KnightsOfRound;

1;
Acme/OppaiEx/Manager.pm

oppaiオブジェクトを管理するパッケージ。oppaiプロパティにoppaiオブジェクトをセットし、showメソッドでそのオブジェクトのoppaiメソッドを呼び出す。

package Acme::OppaiEx::Manager;                                                                                                                                                                             

use strict;
use warnings;

sub new {
  my($class, $oppai) = @_;

  bless {
    oppai => $oppai
  }, $class;
}

sub show {
  my $self = shift;
  print $self->{oppai}->oppai;
}

1;
Acme/OppaiEx/Kuma.pm

クマおっぱいを返すパッケージ

package Acme::OppaiEx::Kuma;                                                                                                                                                                                

use strict;
use warnings;

sub new {
  bless {};
}

sub oppai {
<<'EOS';
      ∩___∩
      | ノ       ヽ (⌒ヽ
     /⌒) ●   ● | / / ミ
  / /   ( _●_)  ミ  /   おっぱい!おっぱい!
 .(  ヽ  |∪|  ` 彡
  \   _ヽノ_  彡
   /  (__ ___/
    |       /
    |  /\ \
    | /    )  )
    ∪    (  \
          \_)
EOS
}

1;
Acme/OppaiEx/KnightsOfRound.pm

円卓の騎士的なおっぱいを返すパッケージ

package Acme::OppaiEx::KnightsOfRound;                                                                                                                                                                      

use strict;
use warnings;

sub new {
  bless {};
}

sub oppai {
<<'EOS';
 \\                                                      //
   \\   お    っ    ぱ    い.                                //
    \\                                                //
      \\                     お    っ    ぱ    い     //
        _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.
      ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡
      (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡
                 _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.
               ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡
               (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.
                |   |     |   |     |   |     |   |    |   |     |   |     |   |
                し ⌒J.    し ⌒J.    し ⌒J.    し ⌒J.   し ⌒J.    し ⌒J.    し ⌒J
EOS
}

1;
実行!
$ oppai

  _  ∩
( ゜∀゜)彡 おっぱい!おっぱい!
 ⊂彡
$ oppai kuma

      ∩___∩
      | ノ       ヽ (⌒ヽ
     /⌒) ●   ● | / / ミ
  / /   ( _●_)  ミ  /   おっぱい!おっぱい!
 .(  ヽ  |∪|  ` 彡
  \   _ヽノ_  彡
   /  (__ ___/
    |       /
    |  /\ \
    | /    )  )
    ∪    (  \
          \_)
$ oppai knights

 \\                                                      //
   \\   お    っ    ぱ    い.                                //
    \\                                                //
      \\                     お    っ    ぱ    い     //
        _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.
      ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡
      (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡
                 _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.     _ _∩.
               ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡  ( ゚∀゚)彡
               (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.   (  ⊂彡.
                |   |     |   |     |   |     |   |    |   |     |   |     |   |
                し ⌒J.    し ⌒J.    し ⌒J.    し ⌒J.   し ⌒J.    し ⌒J.    し ⌒J
できた!

_人人人人人人人人人人人人人人人人人人人人人人_
>まさに、おっぱいのポリモーフィズム多態性)や!<
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄

以上

とりあえず基礎文法はざっと入門した!こんどはWebアプリの作り方に入門してみよー

や...やっと理解できた!(2) JavaScriptのスコープチェーン

前回はJavaScriptのプロトタイプチェーンについて、図解を用いることでなんとか理解できました。今回はスコープチェーンに挑戦してみます。前回と同じく「1. 図解を用いる」「2. 用語を明確に定義する」「3. Standard ECMA-262 3rd editionを情報ソースとする」というアプローチで紐解いて行きます。

用語の定義

・本エントリの文章における表記は、以下の表の「ECMA-262 3rd」に統一する
・本エントリの図における表記は、以下の表の「本エントリの略称」に統一する
・本エントリ内におけるES3とは、Standard ECMA-262 3rd editionを指す

ECMA-262 3rd 本エントリの略称 JavaScript(サイ本)第5版(日本語)
Execution Contexts EC 実行コンテキスト
Variable Object VO 変数定義のために使われるオブジェクト
Activation Object AO Callオブジェクト
Global Object GO グローバルオブジェクト
Function Object FO Functionオブジェクト
Scope Chain SC スコープチェーン
[[Scope]] [[Scope]] 言及なし?

※表中の「JavaScript(サイ本)第5版(日本語版)」は比較のための参考情報

前置き

Execution Contextとは?

JavaScriptのスコープチェーンを理解するためには、まずExecution Contextについて理解する必要がある。Execution Contextとは「論理的にスタックの構造をしたもの」であり、コントロールが実行可能コード(Global Code、Eval Code、Function Codeの3つ)に移ると新しいExecution Contextに入る(ES3 10)。つまり、以下の図のように、コントロールが実行可能コードに移ると新しいExecution Contextがスタックにpushされ、そのコードから戻るとスタックからpopされると捉えることができる。


Execution Contextに入ると何が起きるか?

では、新しいExecution Contextに入ると何が起きるのだろうか?ES3では以下3つの処理が行われると定義されている(ES3 10.1.6)。

(1) Scope Chainの生成・初期化
(2) Variable Instantiationの実行
(3) thisの値の決定

上記それぞれにおいて、具体的にはどのような処理が行われるのだろうか?これは実行可能コードの種類によって異なるとされている。そして実行可能コードには3つの種類のコードが存在し、それぞれ以下の通りである(ES3 10.2)。なお、本エントリでは、このうち(1) Global Codeと(3) Function Codeについて扱う。

(1) Global Code
(2) Eval Code
(3) Function Code

サンプルコード

本エントリでは、以下のサンプルコードについて図解を用いて紐解いていくことにする。

var x = 'xxx';                                                                                                                                                                                              

function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z); //xxxyyyzzz
  }

  bar();
}

foo();
最初にGlobal Objectが存在する

まず、いかなるExecution Contextに入るよりも前に、唯一のGlobal Objectが生成される(ES3 10.1.5)。初期状態のGlobal ObjectにはMathやStringといったBuilt-in objectsとブラウザ環境におけるwindowオブジェクトのようなホストが定義するプロパティがセットされる(この点について今回は割愛する)。


Global Code

◆ 新しいExecution Contextに入る

Global Objectが生成された後、Global Codeにコントロールが移る。すると、新しいExecution Contextがスタックに積まれる。そして冒頭記載したようにこのExecution Contextを元に(1) Scope Chainの生成・初期化 (2) Variable Instantiationの実行 (3) thisの値の決定が行われる。詳細は後述するが、これらの処理はExecution Contextに属するScope Chain(図中SC)、Variable Object(図中VO)、thisの値を決定するものと捉えることができる。

◆ Scope Chainの生成・初期化

新しいExecution Contextに入ると、まずScope Chainの生成・初期化が行われる。では、Scope Chainとは何か?ES3によるとScope Chainとは識別子の検索に使われるオブジェクトの「リスト」のことであり、そのExecution Contextの実行中はwith文かcatchを使わない限り不変である(ES3 10.1.4)。なお「リスト」という概念をどう実装するかはES3では定義されていない。そのため、ここでは純粋な「配列」であると考える。では、Global CodeにおけるScope Chainの生成・初期化はどのようになるのか?これはいたってシンプルで、Global CodeにおけるScope ChainはGlobal Objectのみを含むリストとなる(ES3 10.2.1)。

◆ Variable Instantiationの実行

Scope Chainの生成・初期化が行われると、次にVariable Instantiationが行われる。Variable InstantiationとはVariable Objectという特殊なオブジェクトにプロパティとその値を追加する処理のことである。全てのExecution ContextにはVariable Objectという特殊なオブジェクトが存在し、そのExecution Contextのコードにおける変数と関数の宣言はそのVariable Objectのプロパティとして追加されることになる。なお、どのオブジェクトがVariable Objectになるかはコードの種類によって異なり、Global Codeの場合はGlobal ObjectがVariable Objectとなる(ES3 10.1.3, 10.2.1)。

そしてVariable Instantiationでは以下の順番でVariable Objectにプロパティとその値が追加されていく(ES3 10.1.3)

(1) (Function Codeの場合)仮引数がVariable Objectのプロパティとして追加され、その値として実引数がセットされる。
(2) 対象コード内のFunctionDeclaration(関数宣言。関数式は除く)全てに対して、その宣言における関数名がVariable Objectのプロパティとして追加され、その値として新たに生成されたFunction Objectがセットされる。
(3) 対象コード内のVariableDeclaration(変数宣言)全てに対して、その宣言における変数名がVariable Objectのプロパティとして追加され、その値としてundefinedがセットされる。
※ 必ず1→2→3の順番で実行される

今回のサンプルコードにおいて、Global Codeでは変数xの宣言(VariableDeclaration)と、関数fooの宣言(FunctionDeclaration。仮引数はなし)が行われている。上記のルールに従うと、まず関数fooの宣言が処理され(今回はFunction CodeではなくGlobal Codeのため(1)の仮引数の処理は行われない)、その後変数xの宣言が処理されることになる。では、まず関数fooの宣言の方はどのように処理されるのだろうか?

FunctionDeclarationにおいては、前述したようにその宣言における関数名(ここではfoo)がVariable Object(ここではGlobal Object)のプロパティとして追加され、その値として新たに生成されたFunction Objectがセットされる。新しく生成されたFunction Objectは[[Scope]]プロパティという内部的なプロパティを持ち、その値として現在のExecution ContextのScope Chainが参照しているオブジェクトと同じオブジェクト(ここではGlobal Objectのみ)を参照するリストがセットされる(ES3 13.2)。

これでFunctionDeclarationの処理が済んだので、次にVariable Declarationが処理される。VariableDeclarationにおいては、前述したようにその宣言における変数名がVariable Objectのプロパティとして追加され、その値としてundefinedがセットされる。つまり、今回の例ではxがGlobal Objectのプロパティに追加され、その値としてundefinedがセットされる(この段階ではまだ値が'xxx'にならない)。

◆ thisの値の決定

上記のようにしてVariable Instantiationの実行が済むと、次はthisの値が決定される。thisとは全てのアクティブなExecution Contextに関連づいており、どのような値になるかは呼び出し元のオブジェクトと実行されるコードの種類によって異なり、その値は一度設定された後は不変である(ES3 10.1.7)。そしてGlobal Codeにおいてthisの値は常にGlobal Objectとなる(ES3 10.2.1)。そのためこの時点でthisの値はGlobal Objectとなる。

◆ コードを実行

ここまで来てはじめてコードの実行が行われる。今回のサンプルコードにおいては、プロパティxへの文字列'xxx'の代入。およびfoo()が実行されることになる。ここでxに'xxx'を代入する際には、識別子の探索(ES3 10.1.4)が行われる。具体的には現在のExecution ContextのScope Chainが参照しているVariable Objectを先頭から(図では0から)検索していき、識別子に該当するプロパティが存在したらそこで解決する。今回のサンプルではGlobal CodeのExecution ContextにおけるScope Chainが参照しているのはGlobal Objectのみであり、そこにプロパティxが存在するので、そこへ代入が行われる。

foo() - Function Code

◆ 新しいExecution Contextに入る

Global Codeにおいてfoo()が実行されると、新しいExecution Contextに入る。新しいfoo()のExecution Contextに入ると、Global Codeの時と同じようにScope Chainの生成・初期化やVariable Instantiationが行われる。ただし、Global Codeと異なり今回実行されるコードはFunction Codeである。「Scope Chainの生成・初期化、Variable Instantiation、thisの値の決定」はGlobal Codeのルールではなく、Function Codeのルールが適用される。

◆ Scope Chainの生成・初期化

Function CodeにおけるScope Chainの生成・初期化では、まずScope Chainの先頭にActivation Objectという特殊なオブジェクトがセットされる。なお、Activation Objectとは、Execution CodeがFunction Codeに入った時にVariable Objectとして使われるオブジェクトのことである。Activation Objectはまず関数の引数を表すargumentsプロパティの初期化を行う(argumentsに関する詳細については別の機会に言及する)その後Activation ObjectをVariable ObjectとしてVariable Instantiationが行われる。なお、Activation Objectとは仕様上の概念であり、プログラムからActivation Object自体には直接アクセスできない(Activation Objectのプロパティにはアクセスできる)(ES3 10.1.6)。

その後、呼び出した関数の[[Scope]]プロパティが参照しているオブジェクトがScope Chainにpushされる(ES3 10.2.3)。従って、今回のサンプルコードにおいて、foo()を実行した直後におけるExecution ContextのScope ChainはActivation Object(foo()の実行によって作られたもの。図ではAO_1とする)とGlobal Objectを参照していることになる。なお、ここでのポイントは、あくまでも呼び出した関数の[[Scope]]プロパティが使われるのであって、決してGlobal CodeのExecution ContextにおけるScopt Chainが使われる訳では無いということだ。このことは、より複雑な例になってきた際に重要となる(次回以降に扱う)

◆ Variable Instantiationの実行

先ほど触れたように、Function Codeでは、Activation ObjectをVariable ObjectとしてVariable Instantiationが行われる(ES3 10.2.3)。その点を除いては、Global Codeの時と同じ処理が行われるので、ここでのVariable Instantiation以下のような処理になる。

Function Declarationで宣言された関数をVariable Object(AO_1)にセットする(プロパティはbar、値は新たに生成されたFunction Object。そのFunction Objectの[[Scope]]プロパティの値は、AO_1とGlobal Objectを参照するリスト)

Variable Declarationで宣言された変数をVariable Object(AO_1)にセットする(プロパティはy、値はundefined)

◆ thisの値の決定

Function codeにおいて、thisの値はその呼び出し元オブジェクトから提供される(callやapplyメソッドでthisの値を指定できる)。もし呼び出し元オブジェクトから提供されたthisの値がオブジェクトでなければ(nullの場合も含む)、thisの値はGlobal Objectとなる(ES3 10.2.3)。今回のサンプルコードにおいては特にthisの値は指定されていないので、Global Objectがthisの値となる。

◆ コードを実行

ここでfooコードの実行が行われる。今回のサンプルコードにおいては、プロパティyへの文字列'yyy'の代入。およびbar()が実行されることになる。


bar() - Function Code

◆ 新しいExecution Contextに入る

bar()が実行されると、foo()の時と同じように新しいExecution Contextに入りScope Chainの生成・初期化等が行われる。

◆ Scope Chainの生成・初期化
foo()の時と同じようにScope Chainが生成・初期化される。まず新しいActivation Object(図ではAO_2)がScope Chainの先頭にセットされる。

その後、呼び出した関数の[[Scope]]プロパティが参照しているオブジェクトがScope Chainにpushされる。この段階でbar()のExecution ContextのScope Chainは、先頭からAO_2, AO_1, Global Objectということになる。

◆ Variable Instantiationの実行
Variable Declarationで宣言された変数zがAO_2のプロパティに追加される。そしてその値はundefinedである。

◆ thisの値の決定

foo()の時と同じく、ここで特にthisの値は指定されていないので、thisの値はGlobal Objectとなる。

◆ コードを実行

ここでbarコードが実行され、プロパティzに文字列'zzz'が代入される。

この時点で現在実行中のExecution Context(barのExecution Context)は、以下の図のような構成になっている。

この段階におけるconsole.log(x + y + z);というコードにおいて、識別子x, y, zはそれぞれ以下のように解決されるため、結果はxxxyyyzzzとなる。

x: AO_2にxを探すが見つからない→AO_1にxを探すが見つからない→GOにxが見つかり、その値は'xxx'
y: AO_2にyを探すが見つからない→AO_2にyが見つかり、その値は'yyy'
z: AO_2にzが見つかり、その値は'zzz'

終わり

以上でスコープチェーンに関する基本的な内容は終わりです。次回はスコープチェーンに関するより詳細な内容(evalやwith文を使った例)を見て行きたいと思っています。その後は、クロージャやthisについても紐解いていく予定です。よろしければ是非続編もご覧くださいm(__)m

まさに忍者...JavaScriptの関数は第一級オブジェクト

JavaScriptの関数は「ファーストクラスオブジェクト(第一級オブジェクト)」である。なので、変数に代入したり、配列にセットしたり、他の関数に食わせたりできる。この変幻自在っぷりはすごい。newでコンストラクタになるところなんて変化の術のようだ。無名関数の即実行は影縫いの術みたいだし、callやapplyでthisの値を変えるとこなんて口寄せの術を彷彿とさせる。正に忍者 |--)ノシュッ==卍

変数に代入する

var foo = function() {console.log('foo');};
foo();

配列にセットする

var fnList = [
  function() {console.log('foo');},
  function() {console.log('bar');},
  function() {console.log('piyo');}
];

for(var i = 0; i < fnList.length; i++) {
  fnList[i]();
}

オブジェクトのプロパティにセットする

var obj = {
  foo: function() {console.log('foo');}
}

obj.foo();

関数に引数として渡す

function foo(callback) {
  callback();
}

foo(function() {console.log('呼び出されました!');});

関数の戻り値を関数にする

function foo() {
  return function() {console.log('fooからreturnされた関数だよ!');}
}

foo()();

無名関数を作って、即呼び出す

(function() {console.log('foo');})();

newをつけてコンストラクタ

var User = function(name, sex) {
  this.name = name;
  this.sex = sex;
  this.hello = function() {console.log("Hello! I am " + this.name);};
}

hoge = new User('hoge', 'man');
hoge.hello();

call(もしくはapply)でコールバック関数内のthisを変更

function foo(callback) {
  callback.call(document);
}

foo(function() {console.log(this);});

関数定義時のスコープを束縛

var x = 'hoge';
function foo(callback) {
  callback();
}

(function() {
    var x = 'fuge';
    foo(function() {console.log(x);});
})();

コンボは無限大...

ちょっとしたコンボが以下

オブジェクトのプロパティに、無名関数を即呼び出した結果をセットする
var obj = {
  x: (function() {return 1 + 2;})()
};

console.log(obj.x);
オブジェクトのプロパティにセットして、動的に呼び出す
var Models = {
  User: {
    save: function() {console.log('userがsaveされました');},
    delete: function() {console.log('userがdeleteされました');}
  },
  
  Entry: {
    save: function() {console.log('entryがsaveされました');},
    delete: function() {console.log('entryがdeleteされました');}
  },
      
  Comment: {
    save: function() {console.log('commentがsaveされました');},
    delete: function() {console.log('commentがdeleteされました');}
  }
};

for(var name in Models) {
  Models[name].delete();
}

Models['User'].save();

応用すれば、もっともっと色々なことができるはず。。。まさに忍者|--)ノシュッ==卍

jQueryで$(document).readyを複数実行した場合のthisやvarについて

はじめに

jQueryを使う時は、$(document).ready(handler)やその省略法である$(handler)でdom構築ができてからコードを実行することが多い。(handlerは無名関数などの関数オブジェクト)

で、たまに複数のファイルで何度も$(handler)を呼び出すことがあるのだけれど、その際$()の引数に渡しているhandler内のthisやvar宣言について混乱しがちだったので整理しておく。ちなみに検証環境はChrome ver24、jQuery1.9.1。概念はES3を基準にする。

前提

書き方色々

以下の3つは同義(真ん中は推奨されない)

$(document).ready(handler)
$().ready(handler) //this is not recommended
$(handler)

http://api.jquery.com/ready/

複数呼び出した場合

呼び出した順に全てが実行される。別々のファイルにしていても同じ。

$(function() {console.log('hoge');});
$(function() {console.log('fuge');});
$(function() {console.log('piyo');});

//結果:
//hoge
//fuge
//piyo

jQueryのtutorial

これをふまえて、handler内のthisやvarの挙動をみていく

this

handler内のthisはdocumentオブジェクトになる。なので、例えば最初のhandler内で「this.x」とすれば、後で呼び出したhandler内でも「document.x(もしくはthis.x)」で参照することができる

$(function() {
  this.x = 'xxx';
  console.log(this === document); //true
});

$(function() {
  console.log(document.x); //xxx
  console.log(this.x); //xxx
});

jQuery1.9.1のソースコードを見てみると、以下の部分でhandlerのthisにdocumentオブジェクトをセットしていることがわかる

readyList.resolveWith( document, [ jQuery ] );

(略)

// 最終的には以下の部分でapplyの第1引数にdocumentオブジェクトがセットされる
// これにより、handlerのthisがdocumentとなる
list[ firingIndex ].apply( data[ 0 ], data[ 1 ] )

varあり

関数コード内でvar 宣言した変数は、アクティベーションオブジェクトのプロパティになる。なので、例えば以下のようにすると、後で呼び出したhandlerからxにはアクセスできない(実行コンテキストが変わってしまうから)

$(function() {
  var x = 'xxx'; //xはアクティベーションオブジェクトのプロパティ
});

$(function() {
  console.log(x); //error
});

アクティベーションオブジェクトについては、こちらが詳しい
http://alpha.mixi.co.jp/2011/10763/#ecma-263-3-2-variable-object-in-function-context

varなし

handler内でvar宣言しなかった変数は、globalオブジェクトのプロパティになる。これはスコープチェーンを遡ってグローバルオブジェクトにたどり着くため(逆に、スコープチェーンの途中でvar xと宣言されていれば、そこで解決する)。なので、以下のようにすると後で呼び出したhandlerでもxを参照できる。

$(function() {
  x = 'xxx'; //xはglobalオブジェクトのプロパティ(スコープチェーンを遡るので)
});

$(function() {
  console.log(x); //xxx
  console.log(window.x); //xxx
});

http://stackoverflow.com/questions/1470488/difference-between-using-var-and-not-using-var-in-javascript

複数ファイルで$(handler)を使いつつ、変数を共有

handler内で定義した変数を他のファイルのhandler内と共有するにはどうすればよいか?
色々なやり方があるけれど、グローバル空間をあまり汚さない方がバグを生みにくいと思う。
なので、自分の場合はプロジェクト固有の名前空間となるオブジェクトを作ってグローバルオブジェクトにセットし、
外部に公開したいAPIはそのプロジェクト固有の名前空間のプロパティとしてセットするようなやり方を主に使おうと思う

html

<html>                                                                                                                                                                                                      
<head>
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <script src="a.js"></script>
  <script src="b.js"></script>
</head>
</html>

a.js

$(function() {                                                                                                                                                                                              
  var x = 'xxx';

  App = {
    foo: function() {console.log(x);}
  };
});

b.js

$(function() {                                                                                                                                                                                              
  App.foo();
}); 

や...やっと理解できた!JavaScriptのプロトタイプチェーン

JavaScriptのプロトタイプチェーンについて理解しようとしたのだけど、prototypeとか__proto__とかごちゃごちゃになって、色んなブログを読んでもなかなか理解しきれなくて悶々としていたのだが、図を書いたらパッと理解できた!以下、情報ソースはなるべくECMAScript仕様書(3rd)を元にするようにして書きました

なぜ分かりづらいのか?

そもそも、なぜJavaScriptのプロトタイプチェーンは自分にとってこうも分かりづらかったのだろうか?自分なりに分析してみると、まず、「似ているが違う用語が沢山ある」という点がある。ざっとあげただけでも、「prototypeと__proto__」「__proto__と[[Prototype]]」「FunctionとFunctionオブジェクト」などがある。そして次に、「入り組んだ構造が動的に変化する」という点がある。上記のように似たような用語が沢山出てくる上に、それらの構造が入り組んでおり、しかもそれらが動的に変化していくのだ。これは自分の脳みそではきつい。さらに、__proto__プロパティは処理系によっては実装されていない(ChromeFirefoxならおk)など「環境依存」の部分もある。自分が中々理解できなかったのはこういった要素に原因があるのではと思った。

図にすれば理解しやすくなる!

なので、上記を解決するために「図」を用いることにした。それにより「用語が明確に区別され」「構造を状態ごとに追う事ができる」と思ったからだ。ということで、JavaScriptのオブジェクト構造がどのようになるかを図で可視化していく。図は以下のように「丸はオブジェクト。矢印はプロパティ」というシンプルな図を用いることにする。


サンプルコード

以下のサンプルコードを実行した時のオブジェクト構造を図にする

var C = function (name) {
    this.name = name;
};

C.prototype.x ='xxx';

var c1 = new C('hoge');
var c2 = new C('fuge');

C.prototype.y = 'yyy';

C.prototype = {z: 'zzz'};
var c3 = new C('piyo');

関数の定義

まずはこの部分からみていく

var C = function (name) {
    this.name = name;
};
唯一のオブジェクト「グローバルオブジェクト」

・まず、JavaScriptには唯一のオブジェクト、グローバルオブジェクトが存在する
・グローバルコンテキストで変数を定義すれば、グローバルオブジェクトのプロパティになる
・上記のコードでは、グローバルコンテキストで変数Cにfunction式で生成したオブジェクトをセットしている
・つまり、グローバルオブジェクトのプロパティCにオブジェクトをセットしていることになる

function式で生成されるオブジェクトは「Function」から生成された「Functionオブジェクト」

・では、function式で生成されるオブジェクトとはどのようなオブジェクトだろうか?
・まず前提として、ここではfunction式でオブジェクトを生成しているが、それ以外にもFunctionコンストラクタとfunction文を使ってもほぼ同様のことができる
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Statements/function
・で、function式を実行すると組み込みのFunctionというオブジェクトを元に、新たなオブジェクトが生成される
・Functionを元に生成されたオブジェクトを総称してここでは「Functionオブジェクト」と呼ぶことにする(ECMAScript仕様書(3rd )では、「Function Object」と呼ばれている)
・今回のサンプルにおいて、Cは「Functionオブジェクト」である

全ての「Functionオブジェクト」はprototypeプロパティを持つ

・では、Functionオブジェクトとはどのようなオブジェクトだろうか?
・まず、全てのFunctionオブジェクトはprototypeというプロパティを持つ
・そしてprototypeプロパティの先には通常何らかのオブジェクトがセットされている
・実はFunctionも自身から生成されたFunctionオブジェクトである
・なので、FunctionもCもprototypeプロパティをもつ

全ての「オブジェクト」は__proto__プロパティを持つ

・次にオブジェクト全般について突っ込んでみていく
・まず、全ての「オブジェクト」は内部プロパティ[[Prototype]]を持つ。これはとても重要なポイント。

Internal properties and methods are not part of the language. They are defined by this specification purely for expository purposes. (略)Native ECMAScript objects have an internal property called [[Prototype]].

ECMAScript仕様書(3rd )

・[[Prototype]]は仕様上、内部プロパティとされているが実際に__proto__というプロパティ名でプログラムから扱える実装が多い(ChromeFirefox
・ただし、__proto__が存在しない実装もあるので注意
・ここでは[[Prototype]]を__proto__という呼び名で統一することにする

Functionオブジェクト.__proto__は、Function.prototype

・__proto__プロパティの参照先を詳しくみていく
・まず、「Functionオブジェクト(ここではC)」の__proto__プロパティは、Function.prototypeを参照する

__proto__があると何が嬉しいのか?

・自分自身に存在しないプロパティ/メソッドが呼び出された時、__proto__を辿った先にあるオブジェクトにそのプロパティ/メソッドが無いか探し、あればそれを使う
・例えば、Function.prototypeにはtoStringメソッドが存在する。なので、以下のようにするとtoStringが呼び出せる
・C自身はtoStringメソッドを持っていないが、__proto__を辿った先にあるFunction.prototypeがtoStringメソッドを持っているので、それが呼び出される。

C.toString();
//"function (name) {                                                                                                                                                       
//    this.name = name;
//}"

検証:
(hasOwnPropertyメソッドは、オブジェクトが指定されたプロパティを持っているかどうかを示す真偽値を返す)

C.hasOwnProperty('toString'); //false
Function.prototype.hasOwnProperty('toString'); //true
Functionオブジェクト.__proto__.__proto__は、Object.prototype

・さきほど全てのオブジェクトは__proto__プロパティを持つと述べたが、では、Functionオブジェクトの__proto__はどこまで繋がるのだろうか?
・まず、Functionオブジェクト.__proto__.__proto__は、Object.prototypeである。実はObjectも「Functionオブジェクト(Functionから生成されたオブジェクト)」である。そのため、Objectもprototypeプロパティを持っているのだ
・さらに駆け上り、Object.prototype.__proto__の先は何かというと、それはnullになってそこで終わっている
・Object.prototypeにはhasOwnPropertyメソッドがある。なので、以下のようにするとチェーンを辿ってメソッドが実行できる

C.hasOwnProperty();

検証:
hasOwnPropertyメソッドはObject.prototypeのメソッドである

C.hasOwnProperty('hasOwnProperty'); //false
C.__proto__.hasOwnProperty('hasOwnProperty'); //false
C.__proto__.__proto__.hasOwnProperty('hasOwnProperty'); //true
ObjectとFunctionの__proto__が指す先はFunction.prototype

・全てのオブジェクトは__proto__プロパティを持つので、ObjectとFunctionも__proto__プロパティを持つはず。では、その参照先はどうなっているだろうか?
・ObjectとFunctionはいずれも__proto__プロパティがFunction.prototypeを参照している
・これはつまり、ObjectはFunctionから生成され、FunctionはFunction自身から生成されているといえる
・なので、ObjectもFunctionも、Cと同じく「Functionオブジェクト」であるといえる

検証:

Object.__proto__ === Function.prototype //true
Function.__proto__ === Function.prototype //true
C.__proto__ === Function.prototype //true
C.prototypeは空のオブジェクト

・ここで自分が生成した「C」に話を戻す。Cのprototypeはどのようなオブジェクトだろうか?
・Functionから生成したFucntionオブジェクトのprototypeは、デフォルトではnew Object()により生成されたオブジェクト(つまり空のオブジェクト)となる。なので、その空オブジェクトの__proto__プロパティはObject.prototypeを指す

検証:

C.prototype.__proto__ === Object.prototype //true
Functionオブジェクト.prototypeは、constructorプロパティを持つ

・少し脱線するが、全てのFunctionオブジェクト.prototypeはconstructorというプロパティを持つ
・なので、全てのFunctionオブジェクトはそのprototypeプロパティが参照する先のオブジェクトと相互リンクを張っている構図となる(もちろん、これは後で付け替えられるので常にこの関係が成立するわけではない)

検証:

Object.prototype.constructor === Object //true
Function.prototype.constructor === Function //true
C.prototype.constructor === C //true

prototypeの設定と新しいオブジェクトの生成

ここで冒頭のコードに戻って、以下の部分がどういう意味なのかを見る。

C.prototype.x ='xxx';

var c1 = new C('hoge');
var c2 = new C('fuge');
Functionオブジェクト.prototypeに新しいプロパティを追加する

・Functionオブジェクト.prototypeはオブジェクトなので、プロパティを追加することができる
・なのでC.prototype.x ='xxx'; で新しいプロパティxを追加することができる

Functionオブジェクトをnew演算子付きで呼び出すと、コンストラクタとなりオブジェクトを生成する

・Functionオブジェクトをnew付きで呼び出すと、コンストラクタとなって新しいオブジェクトを生成するようになる(newをつけないと通常の関数呼び出しになる)
・newによって生成されたオブジェクトは、__proto__をそれを生成したFunctionオブジェクトのprototypeに設定する
・なので、var c1 = new C('hoge');とすると、新しいオブジェクトが作成され、そのオブジェクトの__proto__がC.prototypeを参照するようになる

・さらに新しいオブジェクトをnew演算子で作成しても、同じように__proto__の参照先がC.prototypeを参照する
・なのでvar c2 = new C('fuge');とすると、c2の__proto__がC.prototypeを参照するようになる
・結果として、ここではc1とc2が両方とも自身に存在しないxを参照できるようになる

検証:

c1.x //xxx
c2.x //xxx

後からプロトタイプオブジェクトにプロパティを追加する

・では、ここでさらにC.prototypeに新しいプロパティを追加したらどうなるだろうか?冒頭のコードにおける以下の部分である。

C.prototype.y = 'yyy';

・c1もc2もC.prototypeを参照しているので、C.prototypeに追加したプロパティは既に作成したc1とc2に反映される

検証:

c1.y //yyy
c2.y //yyy

プロトタイプオブジェクトを全く新しいオブジェクトに取り替える

・では最後に、C.prototypeの参照先オブジェクトを全く新しいものに取り替えてしまったら、どうなるだろうか?
・冒頭のコードにおける最後の部分である。

C.prototype = {z: 'zzz'};
var c3 = new C('piyo');

・さきほどはC.prototypeに新しいプロパティを追加していたが、そうではなく全く新しいものに取り替える
・さらにその後var c3 = new C('piyo');で新しいオブジェクトを生成する
・すると、先に生成していたc1とc2の__proto__はそのまま残り、新しく生成したc3の__proto__だけが新しいC.prototypeを参照するようになる
・ここで注意しなければいけないのが、__proto__のconstructorプロパティである
・取り替える前は__proto__のconstructorプロパティがCを参照していたが、新しく生成したオブジェクトはただのオブジェクトであるため、c3.constructorはObjectを参照している。constructorが必ずしも自動的に張りなおされるわけではないので注意が必要である

検証:

c1.x //xxx
c2.x //xxx
c3.x //undefined

c1.z //undefined
c2.z //undefined
c3.z //zzz

c1.constructor === C //true
c2.constructor === C //true
c3.constructor === C //false

c1.constructor === Object //false
c2.constructor === Object //false
c3.constructor === Object //true

終わり

長くなったが、これで終わり!図にしたら各オブジェクトとプロパティの違いと参照関係がハッキリして、自分としてはとってもスッキリした!
JavaScriptのもう1つのチェーン、スコープチェーンについても、図にしたら理解できそうなので、今度まとめる予定ですノシ

いつも忘れる「Railsのgenerateコマンド」の備忘録

本当にいっっつも忘れる(T-T) ので、よく使うコマンドだけメモ
なお、網羅的な解説は以下のサイトがよくまとまっている!
railsコマンド(rails) - Railsドキュメント

はじめに

全てのrailsコマンドは-h (or --help)オプションでヘルプが見れる

$ rails -h
$ rails generate -h
$ rails generate scaffold -h

generateのショートカットはg

$ rails g scaffold

generateは-p, [--pretend]でドライランできる

$ rails generate scaffold AdminUser name:string mail:string -p

generateで生成したファイルを削除するにはdestory

$ rails destroy AdminUser

主要generateコマンドで生成されるファイル

デフォルトでは(オプションを与えなければ)、以下のようにファイルが生成される

コマンド名 コントローラ ビュー モデル マイグレーション アセット ルート テスト ヘルパー
scaffold
scaffold_controller × × × ×
controller × ×
model × × × × ×
migration × × × × × ×

scaffold

全部入り

$ rails generate scaffold NAME [field[:type][:index] field[:type][:index]] [options]
NAME モデル名 (単数系)。under_score記法とCamelCased記法どちらでもよい
field カラム名
type
index インデックス(uniqueかindex)
options オプション

$ rails generate scaffold AdminUser name:string mail:string

scaffold_controller

コントローラとビューをデフォルトのアクション(index〜destroyの計7つ)で生成する

$ rails generate scaffold_controller NAME [options]
NAME モデル名(単数系)。under_score記法とCamelCased記法どちらでもよい。
options オプション

$ rails generate scaffold_controller AdminUser

controller

コントローラとビューを生成する

$ rails generate controller NAME [action action] [options]
NAME コントローラ名(単数系か複数系かは用途に合わせて )。under_score記法とCamelCased記法どちらでもよい
action アクション
options オプション

$ rails generate controller AdminUsers index show

model

モデルとマイグレーションを生成する

rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
NAME モデル名 (単数系)。under_score記法とCamelCased記法どちらでもよい
field カラム名
type
index インデックス(uniqueかindex)
options オプション

$ rails generate model AdminUser name:string mail:string

migration

マイグレーションを生成する

$ rails g migration NAME [field[:type][:index] field[:type][:index]] [options]
NAME マイグレーション
field カラム名
type
index インデックス(uniqueかindex)
options オプション

$ rails generate migration AddGroupIdToAdminUsers group_id:integer
マイグレーション実行
$ rake db:migrate

終わり

他にもmailerやintegration_testなどの生成コマンドがあるが、自分はあまり使わないので、必要になったら!

GitとSubversionを併用する

仕事ではSubversion(以下、svn)を使っているのだが、これをgitに置き換えたい。
とはいえ、いきなり全体をgitに移行するのはキツそうなので、以下のようなプロセスを踏もうと思う。

(1) 自分の環境だけgitを使えるようにする(中央リポジトリsvn
(2) 他のメンバー(数名規模)もgitを使えるようにする(中央リポジトリsvn
(3) 中央リポジトリsvnからgitに変える

このエントリでは上記(1)の経緯を書きとめておく。

現状

・以下のように中央にsvnリポジトリが存在していて、各人がそこからチェックアウトしている
・各人はチェックアウトした「svn作業コピー」から「作業用ディレクトリ」にコピーしている(cpやrsyncで)
・各人の「作業用ディレクトリ」のhtdocsはdev環境としてブラウザから閲覧可能
・各人の「svn作業コピー」と「作業用ディレクトリ」は一致しない(バージョン管理しないファイルが沢山ある)
・各人の「svn作業コピー」と「作業用ディレクトリ」の差分管理がとにかく大変な状況

自分の環境だけでもgitにおきかえたい。方法として以下の3つが思いついたので、それぞれ試してみた。
最終的に「(C) git-svnを使う」ことになったのだが、折角なのでそこまでの経緯を記録しておく。

(A) 「svn作業コピー」を「gitリモートリポジトリ」として使う

以下のようにsvn作業コピーをgitリモートリポジトリ(bareではないワーキングツリーを持つリポジトリ)にして、gitローカルリポジトリからpull/pushする形を考えた。

結果: 不採用
理由: gitのリモートリポジトリは基本的にbareリポジトリであることが推奨されるため。


# svnリポジトリからチェックアウトしてsvn作業コピーを作る
$ svn checkout [svnリポジトリ] [svn作業コピー]

# svn作業コピーをgit管理下におく(gitリモートリポジトリを兼任させる)
$ cd [svn作業コピーのトップ]
$ git init

# svnの管理下から.gitと.gitignoreを除去
# ※設定用のエディタにはvimを使用した
# ※参考: http://log.xinu.jp/2011-07-25-1.html
$ export svn_EDITOR="vim"
$ svn propedit svn:ignore ./
.git
.gitignore

# 確認
$ svn status
$ svn status --no-ignore

# gitの管理下から.svnを除去
$ vim .gitignore
.svn

# 確認
$ git status

# gitにコミットする
$ git add .
$ git commit -m 'init'

# 作業ディレクトリ(gitローカルリポジトリ)に移動し
# svn作業コピー(gitリモートリポジトリ)からcloneする
$ cd [作業ディレクトリ|gitローカルリポジトリ]
$ git clone [svn作業コピー|gitリモートリポジトリ]

# なんらかのファイルに変更を加えた後、pushする
$ git add .
$ git commit -m 'test'
$ git push

# エラーが出る!
(略)
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error: 
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error: 
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
(略)

(B) 「svn作業コピー」を「gitローカルリポジトリ」として使う

以下のようにsvn作業コピーをgitローカルリポジトリとして使う構成を考えた。
gitリモートリポジトリを別途用意して、それを中間地点としてpull/pushするような構成。

結果: 不採用
理由: 一応できたが、何度もpushやpullをする必要があって面倒。また複雑なのでミスも多くなりそう。


# >>> ここから
$ svn checkout file:hoge fuge
$ cd [svn作業コピーのトップ]
$ git init
$ export svn_EDITOR="vim"
$ svn propedit svn:ignore ./
.git
.gitignore
$ vim .gitignore
.svn
$ git add .
$ git commit -m 'init'
# <<< ここまでは1.と同じ

# gitのbareリポジトリを作る
$ mkdir [git_bareリポジトリの]
$ cd [git_bareリポジトリ]
$ git clone --bare [svn作業コピー]

# svn作業コピーに移動し、作成したgitのbareリポジトリをgitリモートリポジトリに設定する
$ cd [svn作業コピー]
$ git remote add [リモートリポジトリ名] [git_bareリポジトリ]

# 作業用のディレクトリにcloneする
$ cd [作業用ディレクトリ]
$ git clone [git_bareリポジトリ]

# 作業用ディレクトリでなんらかのファイルに変更を加えた後、pushする
$ git add .
$ git commit -m 'test'
$ git push

# svn作業コピーでgit pull
$ cd [svn作業コピー]
$ git pull

# svnの状態を確認
$ svn status -u

(C) git-svnを使う

結果として定石通りにこれを採用した。

自分の環境での問題は、git管理下におきたくないファイルが異常に沢山あること。
そこで.gitignoreファイルをかなり丁寧に指定した。
.gitignoreの指定方法にについてはこちらのエントリにまとめた。

また、以下のサイトを参考にして作業した。
git svn cloneをやるときは--prefix svn/をつけるべき - DQNEO起業日記
Git - Git と Subversion

# 現在の作業用ディレクトリをいったん退避
$ mv [作業用ディレクトリ] [旧作業ディレクトリ]

# 新しい作業用ディレクトリを作成し、git管理下に
$ mkdir [新作業用ディレクトリ]
$ cd [新作業用ディレクトリ]
$ git init

# git-svnがインストールされているか確認。なければインストール
$ git svn --version

# subversionのリポジトリを取り込む
# <注意!>過去のコミット数が多いと、死ぬほど時間がかかる場合がある(数日かかる場合もあるらしい)ので注意
# 自分の環境では、1000コミットくらいあるリポジトリが15分くらいだった
$ git svn clone -s --prefix svn/ [svnリポジトリ(trunkはつけない)] [clone後ディレクトリ名]

# svnリポジトリの情報確認
# svnリポジトリのログ確認
$ git svn info
$ git svn log

# .gitignoreを細かく指定
$ vim .gitignore
色々な設定

# 旧作業用ディレクトリを新作業用ディレクトリに上書き
$ cp -R [旧作業用ディレクトリの内容] [新作業用ディレクトリ]

# 確認
git status

実運用

こちらのブログに掲載されている内容が大変参考になりました。
git-svnを使うときのベストプラクティス - Life goes on
git-svnの使い方を覚えた - idesaku blog

今後

各人がローカル環境でgitに慣れたらリモートリポジトリ自体をsvnからgitに移管しよう