まさに忍者...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)
複数呼び出した場合
呼び出した順に全てが実行される。別々のファイルにしていても同じ。
$(function() {console.log('hoge');}); $(function() {console.log('fuge');}); $(function() {console.log('piyo');}); //結果: //hoge //fuge //piyo
これをふまえて、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 });
複数ファイルで$(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__プロパティは処理系によっては実装されていない(ChromeやFirefoxならお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__というプロパティ名でプログラムから扱える実装が多い(ChromeやFirefox)
・ただし、__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つのチェーン、スコープチェーンについても、図にしたら理解できそうなので、今度まとめる予定ですノシ
続編書きました!
参考URL
以下のページを参考にさせていただきました
Standard ECMA-262-archive
詳細 ECMA-262-3 シリーズ
プログラマのためのJavaScript (7):プロトタイプ継承の正体
いつも忘れる「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
gitignoreまとめ
Gitで無視ファイルを細かく設定する際にはまったので、メモ
前提
まずは前提を抑えておかないと、はまる
既にトラックされたファイルはgitignoreが効かない
$ git init $ touch hoge.txt $ git add hoge.txt # トラックされた後に $ vim .gitignore # 無視設定しても hoge.txt $ git status # 効かない
既にトラックされたファイルを無視対象にしたければ、git rm --cached
$ git rm --cached hoge.txt # 上記のトラックされたファイルをインデックスから削除すれば(ワークツリーはそのまま) $ git status #gitignoreが効く # もしhoge.txtをcommit済みの場合 $ git commit -m 'delete hoge.txt' # コミットしてリポジトリからも消す
空のディレクトリは管理されない
$ git init $ mkdir aaa # 空のディレクトリを作成 $ git add . $ git status # git管理下にならない
Git管理下にしたければ空ファイルを置けばよい。名前は何でもよいが.gitkeepとするのが慣習
$ touch aaa/.gitkeep # 空のディレクトリに.gitkeepという名前の空ファイルを作成 $ git add . $ git status # git管理下になる
.gitignore vs .gitkeep
http://stackoverflow.com/questions/7229885/gitignore-vs-gitkeep
優先順位
gitignoreは以下4つの方法で設定できて、それぞれ優先順位がある
↑優先度「高」
(1) コマンドラインからの指定
・git ls-filesなどのコマンドがコマンドラインから無視パターンを指定できる。
<例 >
untrackedなファイルから*.txtにマッチするファイルを除いて表示
$ git ls-files -o --exclude-standard -x '*.txt'
(2) .gitignoreファイル
・プロジェクトのメンバー全員で共通して無視したいパターンに使う。
・通常、バージョンコントロール内に入れてプロジェクトメンバー間でシェアする。
・複数の.gitignoreファイルがある場合、階層が深い方が優先される
<例>
.gitignore
*.txt
100/.gitignore
!*.txt
結果
. ├── .gitignore ├── 100/ │ ├── .gitignore │ └── hoge.txt # 無視しない(100/.gitignoreが適用) ├── 200/ │ └── hoge.txt # 無視する └── hoge.txt # 無視する
(3) $GIT_DIR/info/excludeファイル
・自分だけの設定。自分がこのプロジェクトにおいてだけ無視したいパターンに使う。
・例えば、自分だけがこのプロジェクトで使っているデバッグ用の俺俺スクリプトなどに。
・$GIT_DIRは、特に設定していなければワークツリーのトップにある.gitディレクトリを指す
<例>
# このプロジェクトに特有な自分だけのスクリプトmy_script.rbをバージョン管理外に $ vim .git/info/exclude # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ my_script.rb
(4) core.excludesfile(設定用変数)で指定されたファイル
・自分だけの設定。自分がどのようなプロジェクトであっても常に無視したいパターンに使う。
・例えば、vimの.swpやmacの.DS_Storeなど
<例>
# git configコマンドで設定(ファイル名は.gitignoreでなくてもよい) $ git config --global core.excludesfile ~/.gitignore # すると~/.gitconfigに以下のような記載が追加される [core] excludesfile = /Users/hidenorimaehara/.gitignore # 対象ファイルに無視パターンを設定 $ vim ~/.gitignore .DS_Store
↓優先度「低」
無視パターンの書き方
無視パターンの書き方は以下。適用される階層に注意
どこにも/なし
・shell glob pattern(※1)として扱う
・.gitignoreファイルにおいては、その.gitignoreファイルを基準とした相対パスでパターン解析
・つまり、同階層以下全てに適用される(上位階層には適用されない)。
・.gitignoreファイル以外(※2)では、ワークツリーのトップレベル(※3)を基準とした相対パスでパターン解析
<例>
100/.gitignore
*.txt
結果
. ├── 100/ │ ├── .gitignore │ ├── 110/ │ │ └── hoge.txt #無視する │ └── hoge.txt #無視する └── hoge.txt #無視しない(100/.gitignoreの上位階層のため)
※1 http://hgbook.red-bean.com/read/file-names-and-pattern-matching.html#id381291
※2 $GIT_DIR/info/excludeファイルなど
※3 git rev-parse --show-toplevelで確認できる
末尾に/
・ディレクトリだけに適用される(ファイルには適用されない)
<例>
100/.gitignore
[0-9][0-9][0-9]/
結果
├── 100/ │ ├── .gitignore │ ├── 110/ #無視する │ │ └── hoge.txt │ ├── 120 #無視しない(120はファイルであり、ディレクトリでないため) │ └── hoge.txt └── hoge.txt
先頭に/
・その.gitignoreファイルと同階層にだけ適用される
・「/」はその.gitignoreファイルを基点としたパスのことであり、ファイルシステムのルートではない
<例>
100/.gitignore
/hoge.txt
結果
├── 100/ │ ├── .gitignore │ ├── 110/ │ │ └── hoge.txt #無視しない(100/.gitignoreの直下ではないため) │ └── hoge.txt #無視する └── hoge.txt #無視しない
途中に/
・ワイルドカード(*など)が「/」にマッチしないshell glob patternとして解析する
・これは例を見た方がはやい
<例>
100/.gitignore
*/hoge.txt
結果
├── 100/ │ ├── .gitignore │ ├── 110/ │ │ ├── 111/ │ │ │ └── hoge.txt #無視しない(100/.gitignoreを基準とした*/hoge.txtに合致しないため) │ │ └── hoge.txt #無視する │ └── hoge.txt #無視しない(100/.gitignoreを基準とした*/hoge.txtに合致しないため) └── hoge.txt #無視しない
!
・逆の意味になる(無視しない)
・これを用いることで、ホワイトリスト方式が実現できる
<例>
.gitignore
# まず直下のファイルとディレクトリを全て無視する /* # ただし、以下は無視しない !/.gitignore !/100
結果
├── .gitignore #無視しない ├── 100/ #無視しない │ └── hoge.txt ├── 200/ #無視する │ └── hoge.txt ├── fuge.txt #無視する └── hoge.txt #無視する
終わり
Githubには各フレームワークや環境におけるデフォルトのgitignoreが集まっており便利
https://github.com/github/gitignore
PHPを愛する試み 〜self:: parent:: static:: および遅延静的束縛〜
PHPを愛する試みというものを個人的にやっている
今回は、self:: parent:: static:: 遅延静的束縛について図で整理してみた。
スコープ定義演算子 (::)
まず「::」について。これはスコープ定義演算子という。マニュアルには以下のようにある。
スコープ定義演算子 (::)
スコープ定義演算子 (またの名を Paamayim Nekudotayim)、 平たく言うと「ダブルコロン」は、トークンのひとつです。 static, 定数 およびオーバーライドされたクラスのプロパティやメソッドにアクセスすることができます。これらの要素をクラス定義の外から参照する際には、 クラスの名前を使用してください。PHP 5.3.0 以降では、変数を用いてクラスを参照することも可能です。 変数の値に (self や parent、 static といった) キーワードを指定することはできません。
...
http://www.php.net/manual/ja/language.oop5.paamayim-nekudotayim.php
分かりづらいので、自分流に解釈する。例えば、以下の図ように沢山のfoo()があるとして、どのfoo()なのかを決定するための演算子が::である。「::の右側にあるメソッドやプロパティが、::の左側のスコープに属すると決定する演算子」と考えればしっくりくる。
self::やparent::というのは、このスコープを表す方法の1つ。全部で以下の4つがある。
方法 | スコープ | 使える場所 |
---|---|---|
クラス名:: | 明示したクラス | クラス定義の外or中 |
self:: | self::が記載されたクラス | クラス定義の中 |
parent:: | parent::が記載されたクラスの親クラス | クラス定義の中 |
static:: | 直近の "非転送コール" のクラス | クラス定義の中 |
1つずつみていく
クラス名::
クラス名::とすると、スコープは「明示したクラス」となる。
クラス定義の外でも中でも使える。
<?php class C { public static function foo() { D::foo(); //foo()のスコープはD } } class D { public static function foo() { echo 'D_foo' . PHP_EOL; } } C::foo(); //foo()のスコープはC。結果:D_foo
self::
self::とすると、スコープは「self::が記載されたクラス」となる。
クラス定義の中でしか使えない。
<?php class C { public static function foo () { self::bar(); //bar()のスコープはself::が記載されたクラス(C) } public static function bar () { echo 'C_foo' . PHP_EOL; } } class CC extends C { public static function bar () { echo 'CC_foo' . PHP_EOL; } } C::foo(); //C_foo CC::foo(); //C_foo
parent::
parent::とすると、スコープは「parent::が記載されたクラスの親クラス」となる。
クラス定義の中でしか使えない。
<?php class C { public static function bar () { echo 'C_bar' . PHP_EOL; } } class CC extends C { public static function foo () { parent::bar(); //bar()のスコープはparent::が記載されたクラス(CC)の親クラス(C) } public static function bar () { echo 'CC_bar' . PHP_EOL; } } CC::foo(); //C_bar
static::
これが本題。static::とすると、スコープは「直近の "非転送コール" のクラス」となる。
クラス定義の中でしか使えない。
ややこしいのが「直近の "非転送コール" のクラス」というもの。マニュアルにはこう書いてある。
遅延静的束縛 (Late Static Bindings)
...
より正確に言うと、遅延静的束縛は直近の "非転送コール" のクラス名を保存します。 静的メソッドの場合、これは明示的に指定されたクラス (通常は :: 演算子の左側に書かれたもの) となります。静的メソッド以外の場合は、そのオブジェクトのクラスとなります。 "転送コール" とは、self:: や parent::、static:: による静的なコール、 あるいはクラス階層の中での forward_static_call() によるコールのことです。 get_called_class() 関数を使うとコール元のクラス名を文字列で取得できます。 static:: はこのクラスのスコープとなります。
...
http://php.net/manual/ja/language.oop5.late-static-bindings.php
整理すると、
非転送コール: | C::foo()や$c->foo()といったクラス名(もしくはオブジェクト)を明示した呼び出し |
---|---|
転送コール: | self:: parent:: static::もしくはクラス階層の中での forward_static_call() での呼び出し |
遅延静的束縛 | 非転送コール時に、明示されたクラス名(もしくはオブジェクトのクラス名)を保持する機能 |
となる。
これらをふまえて、static::を使ったコードを書いてみる
<?php class C { public static function foo() { static::bar(); // bar()のスコープは直近の "非転送コール" のクラス(C::foo()ならC。CC::foo()ならCC) } public static function bar() { echo 'C_bar' . PHP_EOL; } } class CC extends C { public static function bar() { echo 'CC_bar' . PHP_EOL; } } C::foo(); //C_bar CC::foo(); //CC_bar
C::foo()と呼び出した時とCC::foo()で呼び出した時の動きが変わった!
絵にすると以下のようになる
「直近の非転送コール」とは? 〜例1〜
「直近の非転送コール」が何を表すのか、もう少し詳しくみてみる。
例えば、以下のように途中で転送コール(self::など)をはさんだ場合はどうなるか
<?php class C { public static function foo() { self::hoge(); //self::は転送コール } public static function hoge() { static::bar(); // bar()のスコープは直近の "非転送コール" のクラス(この場合CC) } public static function bar() { echo 'C_bar' . PHP_EOL; } } class CC extends C { public static function bar() { echo 'CC_bar' . PHP_EOL; } } CC::foo(); //CC_bar
転送コールは無視して直近の非転送コールまで遡っていることがわかる
絵にするとこうなる
「直近の非転送コール」とは? 〜例2〜
非転送コールが複数存在する場合はどうか
<?php class C { public static function foo() { C::hoge(); //非転送コール } public static function hoge() { static::bar(); // bar()のスコープは直近の "非転送コール" のクラス(この場合、C) } public static function bar() { echo 'C_bar' . PHP_EOL; } } class CC extends C { public static function bar() { echo 'CC_bar' . PHP_EOL; } } CC::foo(); //C_bar
非転送コールが複数存在する場合は、直近の非転送コールに解決されている
絵にするとこうなる
いずれにおいてもstatic::からコールスタックを遡って、一番近い非転送コール(転送コールは除く)で明示されたクラスに解決されていることがわかる。
静的メソッドの呼び出しではない場合
これまで静的メソッドの呼び出しを例としてきたが、さきほど引用したマニュアルにあるように、「静的メソッド以外の場合は、そのオブジェクトのクラス」となる
<?php class C { public function foo() { static::bar(); } public static function bar() { echo 'C_bar' . PHP_EOL; } } class CC extends C { public static function bar() { echo 'CC_bar' . PHP_EOL; } } $cc = new CC(); $cc->foo(); //CC_bar
静的メソッドでなくても直近の非転送コールまで遡って行く仕組みは同じだ。
これを絵にするとこうなる
注意:「メソッド・プロパティ定義のstatic」「静的変数static」とは違う
static::について混乱するのは、staticが遅延静的束縛以外の意味でも使われるからだ。マニュアルには以下のように書いてある
static キーワード
このページでは、static キーワードを使って静的なメソッドやプロパティを定義する方法を説明します。 static は、 静的な変数の定義 や 静的遅延束縛 にも使えます。これらの場合の static の使い方は、 それぞれのページを参照ください。
http://php.net/manual/ja/language.oop5.static.php
つまり、staticはこれまで見てきたように遅延静的束縛以外に、以下2つの意味で使われる。
・静的なメソッド・プロパティを定義するためのstatic
<?php class C { public static $v = 'hoge'; public static function foo() { echo 'foo' . PHP_EOL; } } var_dump(C::$v); //hoge C::foo(); //foo
・ 静的変数のstatic
<?php class C { public function countup() { static $count; //静的変数 $count++; var_dump($count); } } $c = new C(); $c->countup(); //1 $c->countup(); //2 $c->countup(); //3
同じキーワードが色々な意味で使われるところがややこしい。。。
以上
長かったがstaticについては大分整理できた。
余談
self::は__CLASS__。static::はget_called_class()で参照できる
<?php class C { public static function foo() { var_dump(__CLASS__); //常にC self::bar(); //常にC::foo() var_dump(get_called_class()); //CかCC static::bar(); //C::foo()かCC::foo() } public static function bar() { echo 'C_bar' . PHP_EOL; } } class CC extends C { public static function bar() { echo 'CC_bar' . PHP_EOL; } } C::foo(); CC::foo();
new self() new parent() new static()
self, parent, staticキーワードはオブジェクト生成にも使える。
<?php class C { public static function foo() { var_dump(new self()); } public static function hoge() { var_dump(new static()); } } class CC extends C { public static function bar() { var_dump(new parent()); } } C::foo(); //object(C) CC::bar(); //object(C) C::hoge(); //object(C) CC::hoge(); //object(CC)