や...やっと理解できた!(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)
複数呼び出した場合
呼び出した順に全てが実行される。別々のファイルにしていても同じ。
$(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