■
檜山正幸さんの記事にインスパイアされて、というか、内容を理解するために、メモ。
データ、オブジェクト構造、コンパイルと実行、宣言とスコープ、関数、オブジェクト生成、プロトタイプチェーン
まずは、データについて。データは6種類に分かれる。
number型、boolean型、string型、object型、function型、undefined型の6つ。
リテラル、変数、式、をtypeof演算すると上の6つの文字列のどれかが返される。
number、boolean、string、null, undefinedはイミュータブル(変更不可能)である。つまり、演算を作用させてもデータは変更されない。
イミュータブルなデータは単一のメモリブロックによって表現される。位置情報は無関係。等価性は、そのメモリブロックのビット列の等価性で決定する。
対して、objectはミュータブル(変更可能)である。つまり演算の作用として、データを変更できる(というか変化することが期待されるデータである)。オブジェクトは「性質」と「振る舞い」を持つことが期待される、つまり、データには値と、位置情報があり、プログラムはデータ位置情報から値を参照する。
※stringは実装上はおそらく参照を使っている。けれど、言語仕様としてはprimitiveとして扱っている。
number, boolean, stringは以上のとおり、primitiveである。関数Number, Boolean, Stringそれぞれが、primitive->object型変換を行う(型変換関数によって返されるオブジェクトはいわば「ラッパーオブジェクト」である。)
次にオブジェクト構造。
オブジェクトの情報と機能は、必ずオブジェクトの名前を通じてアクセスされる。この名前を「プロパティ」と言う。
そのイメージは、グラフである。
ノードがデータそのものであり(プリミティヴなデータも含まれる)、ノードから他のノードへの辺が参照である。辺には「必ず」名前が付けられ、それがプロパティ。(プロパティがノードに直接くっついていないことに注意)。オブジェクトたちは、このグラフ構造(ツリー構造ではない!従って循環もあり得る)の中に存在している。
javascriptには無名のグローバルオブジェクトが必ず見えている。これが、グラフ構造におけるルートとなる。ブラウザであれば、windowという名前がそれをさす。あるいは、トップレベルではthis演算子がグローバルオブジェクトを指す。
次に、コンパイルと実行。
javascriptのソースコードは、「1:コンパイラによって処理系(javaでいうVMみたいなもの)に依存したコード列にコンパイルされ」、「2:そのコード列がそのVMによって実行される」。コンパイル中も、実行中もオブジェクト構造(上で述べたグラフ構造)は変化する(だからこそオブジェクトには位置情報があるわけで)。また、コンパイル後、直ちに実行されるので、ユーザはコンパイルを意識する必要がない(つまりコンパイルされたコード列(=トップレベルのコード列)は実行後は不要となる)。コンパイル単位は、ブラウザであればファイルである。つまりファイルに格納されたコード全てが一度にコンパイルされる(1行ごと、ではない)。
全体を眺める(これを「系」と呼ぶのが用語として正しいか・・・)と、javascriptの処理系は、コンパイラ・VM・オブジェクト構造からなる、ととらえられる。この系の中で、全てが行われる。プログラム走行中に起こることは全てオブジェクト構造に記録される。実装はブラウザやその他etcさまざまであるが、処理系はこのオブジェクト構造を全て把握している(つまりプロパティを知っている)。ユーザー(=プログラマ)に開放されていないプロパティもあり、それは処理系によって利用される。
次に宣言とスコープ。
function文、とvar文は、宣言である。宣言とは、コンパイラに出す指示、と考えてみる。すなわち、function文,var文は、コンパイルフェーズで処理される。ソースコード上の全ての関数定義(関数式でない!!)は、コンパイルフェーズで実行可能なオブジェクトとしてヒープに存在しているので、いつでも実行できる。var文は、コンパイル時に変数をメモリブロック内に確保し、undefined値で初期化される。undefinedは0, null, NaNとも違う値で、”未定義”を意味する値である。グローバルオブジェクトのプロパティとしてundefinedプロパティがあり、これはundefinedという値をさしている。
javascriptにはグローバルスコープとローカルスコープの二つしかない。グローバルスコープとは、グローバルオブジェクトそのものであり、ローカルスコープとは呼び出しいてる関数オブジェクトそのものである。そう考えると、「変数」というのは、スコープのプロパティと考えてよい(クロージャ・イベントハンドラは別)。
「したがって」ブロックスコープは存在しない。
次に関数。
関数もこの中にあっては、オブジェクトであるので、プロパティ(&内部プロパティ)を持つ。関数の持つデータはコード(=上で述べたコンパイル済コード列=ビット列)である(これはユーザーには開放されていない)。
関数を呼び出す、あるいは実行する、というのは、適当なセットアップの後、関数オブジェクトの、上で述べた内部プロパティの指すコード列を処理系の中で実行することである。このセットアップとしてコンパイルがある。
トップレベルの関数のソースコード一つ一つから、コンパイラが処理系に依存したビット列を生成し、さらにそれに対応した関数オブジェクトと、グローバルオブジェクトも生成する。こうして、関数は呼び出すことのできる(=実行できる)オブジェクトとなる。
次にオブジェクトの生成。
オブジェクトはnew演算子と関数オブジェクトで行う。ここでは、new演算子の作用する関数オブジェクトのことをコンストラクタという、という風に理解してみる。なお、リテラル表記もあるけれど、これは処理系が生成と初期化を同時に行ってくれている。***生成・と初期化は別である!!***
new演算子は次のことを行う。
まず、処理系が中身のないオブジェクトを生成し、ヒープにアロケートする。次に、コンストラクタのthisにこのオブジェクトをセットする、次に、コンストラクタを実行、最後に、このオブジェクトの内部プロパティ__proto__にコンストラクタのprototypeプロパティをセットしてオブジェクトが返される。
関数オブジェクトには、prototypeプロパティが必ずあり、prototypeプロパティなにかしらの原型オブジェクトを指している。オブジェクトの生成、つまりnew演算子は、その原型へのチェインを形成するために、__proto__にその情報を渡している。__proto__にはconstructorプロパティあり、これがnew演算子の作用子であるコンストラクタオブジェクトを指している(=親を認識できる、自分と親を区別できるということ)。
コンストラクタオブジェクトにはprototypeプロパティがあり、それは上でも述べた原型となるオブジェクト(これをプロトタイプオブジェクトと呼んでみる)を指している。
そしてプロトタイプオブジェクトオブジェクトには、constructorプロパティがあり、それは、コンストラクタオブジェクトを指している。
つまり、prototypeプロパティとconstructorプロパティはそれぞれ対を成していて、それぞれプロトタイプオブジェクト・コンストラクタオブジェクトを指している。
次にプロトタイプチェーンについて。
javascriptの全てのオブジェクトには__proto__という内部プロパティがある。__proto__には以下の性質がある:
1:__proto__の値は、nullもしくは自分とは違うオブジェクトである。
2:x.__proto__, x.__proto__.__proto__, x.__proto__.__proto__.__proto__, ・・・という列(これをプロトタイプチェーンという)はxには戻らない。
3:2のチェーンは、nullが出現した時点で終わる。
このチェーンは、プロパティの検索、つまり"."演算子の処理に用いられる。"."演算子は、作用するオブジェクトのプロパティを検索するが、自身にプロパティを見つけられない場合は、このチェーンをたどって、プロパティを検索し、そのプロパティの指すオブジェクトを返す。無い場合は、undefinedを返す。
次に一応メソッド、について。
メソッドとは、関数オブジェクトを値に持つプロパティである。なので、メソッドの検索も、プロトタイプチェーンを使って行われる。
javascriptあれこれ備忘録
javascriptに関する覚え書き。
関数は"first class"オブジェクトである。ファーストクラスだと、
- 無名リテラルで表現OK
- 変数に格納OK
- データ構造に格納OK
- 関数に引数として渡せる
- 関数の戻り値にできる
- 実行時に定義できる
- 関数名と変数名は無関係
などなど。。。
//無名リテラルで表現OK function(){ //<--- 名前の無い関数リテラルを宣言 return "boo"; } //変数に格納OK var hoge = function(){ //<--- 変数hogeを宣言し、無名関数で初期化している return "boo"; }; //関数名と変数名は無関係 function foo() {} alert(foo); // 関数名 "foo" を含む文字列をアラートする var bar = foo; alert(bar); // アラートする文字列には変わらず関数名 "foo" が含まれている //実行時に定義できる->つまりクロージャーを作成できる function hoge(){ var daba = "Hello"; return bobo(); function bobo(){ return "Hey" + daba; } }
変数の宣言方法
変数はvar キーワードで宣言する。
グローバルスコープにおいて宣言された変数は、グローバルオブジェクトのプロパティとして作成される。
ブラウザの場合、windowオブジェクトのプロパティとなる。
宣言されていない変数にアクセスしようとするとReferenceError 例外が発生する。
初期化されていない変数にはundefinedがセットされる。
関数を定義する3つの方法とそれぞれの違い
//function文で定義(これは関数宣言) //この場合、関数を定義しているスコープに関数名と同じ名前の変数が作成され、この関数が代入される function name([param[, param[, ... param]]]) { statements } //function演算子で定義(これは関数式) //この場合Functionオブジェクトを返す。nameを指定すれば名前付きのFunctionオブジェクトを返す。 function [name]([param[, param[, ... param]]]) { statements } //Functionコンストラクターで定義 new Function([param[, param[, ... param]]], functionBody) //"function"キーワードは、文脈によって、関数宣言にも、関数式にもなる。 //式の一部となっている場合、if文内など、ネストの中にある場合などのばあい、、 //functionキーワードは関数式として構文解析される。 ただし、ブラウザによっては(IE8など)名前付き関数式を関数宣言解釈するものがあるので、注意。
関数には名前を付けることができるが、その名前は自身の関数内からのみ参照可能である。
function something_001(){} alert(something_001); //<--- function something_001(){}と表示される var bobo = function something_002(){}; alert(something_002); //<--- エラーになる(グローバルスコープからsomething_002にはアクセスできない) //上のsomething_001の例は、 var something_001 = function something_001(){} //と同じ。だからエラーにならない。
ホイスティング
あるスコープ内の変数宣言・関数宣言は、その位置に関わらずスコープの先頭に持ち上げられる(ホイスティングという)。
潜在エラーを回避するため、変数宣言はできる限りスコープの先頭で行うのがよい。
var myvar = "my value"; (function() { console.log(myvar); // undefined var myvar = "local value"; })(); hogehoge(); function hogehoge(){ alert("HOGE----");// HOGE----がアラートされる } //さらに、関数宣言は初期化されていない変数宣言をオーバーライドする var hogehoge; alert(hogehoge); //<--- function(){〜}がアラートされる var hogehoge = "hogehoge"; alert(hogehoge); //<--- "hogehoge"がアラートされる
スコープ
javascriptでは、ソースコード内の位置によって変数のスコープが作成される(レキシカルスコープという)。
関数内で宣言された変数のスコープは、関数内のみ。同様に引数のスコープもその関数内のみ。
単なるコードブロックはスコープを作らない。オブジェクトによってスコープが作られる。従って、if文なども同様にスコープを作らない。
var x = 1; { x = 2; } console.log(x) //2
//スコープチェーン
あるスコープ内に変数定義が見つからない場合、上位のスコープにさかのぼって変数定義をサーチする。
this 演算子
関数をコールする際に、”."演算子で結びつけられているオブジェクトの参照が、thisに渡される。結びつけられていない場合はグローバルオブジェクトの参照がthisに渡される。
グローバルスコープでのthisは、グローバルオブジェクトの参照を返す。
関数内のthisは、それを利用しているオブジェクトへの参照を返す。
new演算子
new foo(...)の場合、次のステップで演算が行われる:
Abstract Factory Pattern
<?php interface AssetA_Base{ function method(); } interface AssetB_Base{ function method(); } interface AssetC_Base{ function method(); } class AssetAX implements AssetA_Base{ function method(){} } class AssetBX implements AssetB_Base{ function method(){} } class AssetCX implements AssetC_Base{ function method(){} } class AssetAY implements AssetA_Base{ function method(){} } class AssetBY implements AssetB_Base{ function method(){} } class AssetCY implements AssetC_Base{ function method(){} } interface FactoryBase{ function create_asset_a($arg); function create_asset_b(); function create_asset_c(); } class FactoryX implements FactoryBase{ function create_asset_a($arg){ return new AssetAX; } function create_asset_b(){ return new AssetBX; } function create_asset_c(){ return new AssetCX; } } class FactoryY implements FactoryBase{ function create_asset_a($arg){ return new AssetAY; } function create_asset_b(){ return new AssetBY; } function create_asset_c(){ return new AssetCY; } } class FactoryZ implements FactoryBase{ function create_asset_a($arg){ return new AssetAY; } function create_asset_b(){ return new AssetAY; } function create_asset_c(){ return new AssetBX; } } echo "case of FactoryX<br />"; $factory_x = new FactoryX; var_dump($factory_x->create_asset_a(123)); var_dump($factory_x->create_asset_b()); var_dump($factory_x->create_asset_c()); echo "<hr />"; echo "case of FactoryY<br />"; $factory_y = new FactoryY; var_dump($factory_y->create_asset_a(456)); var_dump($factory_y->create_asset_b()); var_dump($factory_y->create_asset_c()); echo "<hr />"; echo "case of FactoryZ<br />"; $factory_z = new FactoryZ; var_dump($factory_z->create_asset_a(456)); var_dump($factory_z->create_asset_b()); var_dump($factory_z->create_asset_c()); ?>
Builder Pattern
<?php abstract class TagBuilderBase{ //methods are POLYMORPHIC!! abstract function build_method_a(); abstract function build_method_b(); abstract function build_method_c(); final function base_method(){ echo "Processed : " . __METHOD__ ."</br>"; } } class LinkTagBuilder extends TagBuilderBase{ function __construct(){ $this->base_method(); } function build_method_a(){ echo "Processed : " . __METHOD__ ."</br>"; } function build_method_b(){ echo "Processed : " . __METHOD__ ."</br>"; } function build_method_c(){ echo "Processed : " . __METHOD__ ."</br>"; } } class ImageTagBuilder extends TagBuilderBase{ function __construct(){ $this->base_method(); } function build_method_a(){ echo "Processed : " . __METHOD__ ."</br>"; } function build_method_b(){ echo "Processed : " . __METHOD__ ."</br>"; } function build_method_c(){ echo "Processed : " . __METHOD__ ."</br>"; } } class Controller{ protected $builder; function __construct($builder){ $this->builder = $builder; } function build(){ //DELEGATE!!! $this->builder->build_method_a(); $this->builder->build_method_b(); $this->builder->build_method_c(); } } class Main{ static function creates_link_tag(){ $link_tag_builder = new LinkTagBuilder(); $controller = new Controller($link_tag_builder); $controller->build(); } static function creates_image_tag(){ $image_tag_builder = new ImageTagBuilder(); $controller = new Controller($image_tag_builder); $controller->build(); } } Main::creates_link_tag(); Main::creates_image_tag(); ?>
Builder Pattern vs Abstract Factory Patternその2
Abstract Factory Patternって、どういう局面で使えばいいのか分からなくて、調べてみたらDBアクセスの部分で使う事ができるみたい。
適用例を通じて分かったが、Builder PatternとAbstract Factory Patternって全く別物ですね。
Railsで言えば、ActiveRecord方面にAbstract Factory Patternがでてくるっぽい、、、ActionView::Helpersまわりは、Builderパターンそのものなのかな、、、、みたいな。。。
とりあえずこの程度の理解で良い気がする。
Composite Pattern
木構造を持つオブジェクトモデルに適用可能なパターン。O/Rマッピングにおける、単一テーブル上の自己参照結合に適用できる。
<?php /* * オペレーションエラー時の例外クラス */ class FileTreatmentException extends RuntimeException{ function __construct($message){ parent::__construct($message, $code); } } /* * ツリー構造を持つ集合要素の抽象クラス */ abstract class Component{ protected $name; protected $size; /* * プロパティへのアクセッサー */ abstract function name(); /* * プロパティへのアクセッサー */ abstract function size(); /* * * プロパティへのアクセッサー */ abstract function render(); } /* * ツリー構造の葉クラス */ class Leaf extends Component{ function __construct($name, $size){ $this->name = $name; $this->size = $size; } function name(){ return $this->name; } function size(){ return $this->size; } function render(){ echo "<li><b>Name</b>: {$this->name}, <b>Size</b>: {$this->size}</li>\n"; } } /* * ツリー構造の枝クラス */ class Composite extends Component{ protected $children = array(); function __construct($name){ $this->name = $name; } function name(){ return $this->name; } function size(){ foreach($this->children as $component){ $this->size += $component->size(); } return $this->size; } function render(){ echo "<p>{$this->name}</p>\n"; echo "<ul>\n"; foreach($this->children as $component){ $component->render(); } echo "</ul>\n"; } /* * エントリーオペレーション * エントリーを追加する */ function add(Component $component){ $this->children[$component->name()] = $component; } /* * エントリーオペレーション * エントリーを削除する */ function remove(Component $component){ unset($this->children[$component->name()]); } /* * エントリーオペレーション * エントリーを取得する */ function children(){ return $this->children; } } $leaf_1 = new Leaf("椿", 234); $leaf_2 = new Leaf("松", 195); $leaf_3 = new Leaf("樫", 3298); $leaf_4 = new Leaf("楢", 275); $eda_1 = new Composite("枝"); $eda_1->add($leaf_1); $eda_1->add($leaf_2); $eda_2 = new Composite("枝2"); $eda_2->add($leaf_3); $eda_1->add($leaf_4); $eda_1->add($eda_2); $eda_1->render(); $eda_2->add("hoge");//Catchable fatal errorが発生 ?>