ECMAScript6 let・ループ・ブロックスコープ
ES5以下がだいだい分かってきたような気がしたから、ES6に手を出していくことにした。
以下の挙動が意味不明だった。
var callback = []; for (let i = 0; i < 3; i++) { callback[i] = function () { console.log(i); }; } callback[0](); // 0 callback[1](); // 1 callback[2](); // 2
だいだい、letはブロックスコープ内で再定義できないはずなのに、何でループできてるのか…?
ついでに、let を var に変えるとこうなる。
var callback = []; for (var i = 0; i < 3; i++) { callback[i] = function () { console.log(i); }; } callback[0](); // 3 callback[1](); // 3 callback[2](); // 3
変数iはグローバル変数なので、関数実行時の値が参照される。
0, 1, 2 ... としたい場合、ES5なら以下のように書く。
var callback = []; for (var i = 0; i < 3; i++) { (function (i) { callback[i] = function () { console.log(i); }; }(i)); } callback[0](); // 0 callback[1](); // 1 callback[2](); // 2
即時関数に引数から変数iを渡す際、即時関数内で変数iが新たに定義される。
グローバルの変数iと、即時関数スコープ内の変数iは別物となる。
ついでに言うと、callback配列内の各関数はクロージャとなる。変数iは、各関数ごとに独立している。
話は初めのletに戻るが、そもそもループの解釈を勘違いしていた。
以下のように、ループ毎に、新たにブロックスコープが作られるイメージが正しいと思う。
{ let i = 1; function callback1() { console.log(i); } } { let i = 2; function callback2() { console.log(i); } } callback1(); // 1 callback2(); // 2
ループ毎にletが定義されていると考えると、初めの挙動にも納得がいく。
JavaScript バブリングの活用
例えば、以下のようなHTMLがあるとする。
<ul> <li>1</li> <li>2</li> <li>3</li> <li>4</li> ... x 1000 ! </ul>
<li>をクリックした時のイベントハンドラを登録したいとする。
jQueryで普通に書くとこうなる。
$('li').on('click', function () { console.log(this.textContent); });
(19ms)
ピュアJSで書くとこうなる。
var liArray = document.querySelectorAll('li'); for (var i = 0; i < liArray.length; i++) { liArray[i].addEventListener('click', function () { console.log(this.textContent); }); }
(17ms)
この場合、1000個の<li>にイベントハンドラが登録される。
1000個に登録なんて…、色々と効率が悪い気がする。(多分)
そこで、バブリングを利用して、同様の機能を実現する。
親ノードの<ul>にイベントハンドラを登録する。
var ul = document.querySelector('ul'); ul.addEventListener('click', function (event) { event.stopPropagation(); console.log(event.target.textContent); }, true);
(0.04ms)
addEventListenerの第3引数にtrueを設定することで、イベントが発火されたノード<li>をすっ飛ばして、<ul>が発火される。
stopPropagation() とすることで、そこで処理は終了。<li>のイベントは発火されない。
後々、DOM操作で<li>が追加されても、<li>にイベントハンドラを登録し直す必要は無い。
バブリング最高や!
Drupal8 FormクラスでGET通信を使う
カスタムモジュールで、Formクラスを使う時の話。
デフォルトはPOST通信になる。
「設定を変えればGET通信なんて余裕だぜ、はははー」
なんて思いつつ、以下に設定。
<?php $form['#method'] = 'get';
うーん、だめ。動かない。なぜ?
悶々と調べた結果、Drupal8コアのバクとしか思えないんだが…。
以下、コアから引用。
<?php // 'GET' が設定される? $form_state->setRequestMethod($request->getMethod()); // 'GET' 時の分岐 <?php $input = $form_state->isMethodType('get') ? $request->query->all() : $request->request->all();
実際、一つ目のメソッドで 'GET' は設定されない。
以下のように、setRequestMethod ではなく、setMethod を使う必要がある。
<?php $form_state->setMethod($request->getMethod());
ということで、この辺りの処理を、コアから外に出して、手動で行う。
コントローラにて。
<?php $formState = new FormState(); $formState->setMethod('get'); $form = \Drupal::formBuilder()->buildForm('\Drupal\sample_form\Form\ExampleForm', $formState);
上記のように書くと、何事も無かったかのように、FormクラスがGET通信で動く様になる。
JavaScriptのコンストラクタとプライベート変数
まず、コンストラクタを使うこと前提で。
コンストラクタ内でメソッドを定義する場合、プライベート変数を使うことができる。
よくあるカウント機能を持つサンプル。
function Nogi() { var count = 0; this.say = function () { count++; console.log(count); }; }
インスタンス毎に、プライベート変数 count が別々にカウントアップされていることが分かる。
var nogi = new Nogi(); nogi.say(); // 1 nogi.say(); // 2 nogi.say(); // 3 var nogi2 = new Nogi(); nogi2.say(); // 1 nogi2.say(); // 2 nogi2.say(); // 3
しかし、インスタンスを作るごとに、this.say()がコピーされてしまう。
普通、メソッドは prototype に定義する。
だが、prototypeでメソッドを定義する場合、プライベート変数を使うことはできない。
それっぽいことをやってみる。
function Nogi() {} Nogi.prototype.say = (function () { var count = 0; return function () { count++; console.log(count); } }());
インスタンスを複数作り、カウントアップ。
var nogi = new Nogi(); nogi.say(); // 1 nogi.say(); // 2 nogi.say(); // 3 var nogi2 = new Nogi(); nogi2.say(); // 4 nogi2.say(); // 5 nogi2.say(); // 6
変数 count が共用されている。そりゃ prototype 自体が共用されているので当たり前か。
count はクラス変数になっているとも言える。
ES6ならやりたいことが出来るかも。
でも今はES6のことは考えたくありません。さようなら。
Observerパターンについて考える
Observerパターン、名前は知ってるけど、いまいち使いどころがよく分かりませんでした。
「状態の変化を通知する仕組み」とのことですが、あまりそういう状況に出くわさないというか、頭にピンとこないというか…。
調べてみたんですが、おそらく自分は勘違いしてました。
これ、別に通知とか関係ないのでは。
結局のところ、
<?php $objects = [ new ObjectA(), new ObjectB(), new ObjectC() ]; foreach ($objects as $object) { $object->commonMethod(); }
上記のように、「同名のメソッドを持つなら、ループできるよ」というだけのように思います。
<?php interface Object { public function commonMethod(); }
インターフェースを作って、共通のメソッドを持たせます。
そしてもう一点、「状態変化をどのObserverに通知するか(共通メソッドをどのオブジェクトに実行させるか)」ですが、新人の頃の自分なら、わざわざフラグ用の変数を作っていたように思います。
<?php $objectA->isObserving = true; $objectB->isObserving = false; $objectC->isObserving = true; $objects = [$objectA, $objectB, $objectC]; foreach ($objects as $object) { if ($object->isObserving) { $object->commonMethod(); } }
でもそうじゃなく、Subject(メインルーチン)にObserver(メソッドを実行させるオブジェクト)を配列で持たせてやれば、それでいいんですよね。
<?php $subject->observerList = [ $ObjectA, $ObjectC ]; foreach ($subject->observerList as $object) { $object->commonMethod(); }
フラグ用の変数を作るのは、今後控えようと思います。フラグを立てたいオブジェクトを配列に持たせてやれば、それでよい!
結局、JavaScriptはどう継承すればいいのか
結局、JavaScriptはどう継承すればいいのか。
調べてもサイトによって書いてる事が違うので、ひとまず、信用できそうな方法を以下にまとめます。
(非 ES6 です。)
乃木坂46の秋元真夏を親クラス、相楽伊織を子クラスとします。
2期生の相楽は、1期生秋元の意思を継いでるわけですね。
prototype継承用の関数。
一時的なプロキシコンストラクタを使います。
// prototype継承用の関数 var inherit = (function() { var F = function() {}; // クロージャを利用 return function(C, P) { F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; } }());
こちら、親クラス。
// 親クラス var Parent = function() { this.group = '乃木坂46'; this.name = '秋元真夏'; } Parent.prototype.shoot = function() { console.log(this.group + 'の' + this.name + 'です。ズッキュン!'); }
子クラスです。
applyメソッドで、親コンストラクタのプロパティを継承。
inherit関数で、親のprototypeに設定されたメソッドを継承してます。
// 子クラス var Child = function() { Parent.apply(this, arguments); // 親のプロパティを継承 this.name = '相楽伊織'; } inherit(Child, Parent); // 親のメソッドを継承
実行。相楽伊織がズッキュンを継承してくれました。
var child = new Child; child.shoot(); // 乃木坂46の相楽伊織です。ズッキュン!
うーん、まだprototypeを使いこなしてる感じがしないなぁ。
もう少し粘着して覚えていきます。
デコレータパターンでテンプレートメソッドを使う(PHP)
うーん、今までデコレータパターンを勘違いしてた。
デコレータパターンは継承と書き換え可能でした。
デコレータは動的に継承っぽい関係を作れるので、その点メリットがあります。
ただ、どの程度書き換え可能なのか…?
自分はテンプレートメソッドをよく使うんですが、そういうこともデコレータは出来るのか…?
というわけで、デコレータパターンでテンプレートメソッドを再現してみました。
ひとまず、一旦、普通の継承を。
<?php class Super { public function render() { echo '<p>2</p>'; $this->tmplMethod(); echo '<p>4</p>'; } } class Child extends Super { public function render() { echo '<p>1</p>'; parent::render(); echo '<p>5</p>'; } public function tmplMethod() { echo '<p>3</p>'; } } $obj = new Child(); $obj->render();
こいつを、デコレータで書き換える。
親となるクラス。
<?php class Super { public function render(callable $callback) { echo '<p>2</p>'; $callback(); echo '<p>4</p>'; } }
子供のクラス。
<?php class Child { public function __construct(Super $super) { $this->super = $super; } public function render() { echo '<p>1</p>'; $this->super->render([$this, 'tmplMethod']); echo '<p>5</p>'; } public function tmplMethod() { echo '<p>3</p>'; } }
実行。
<?php $obj = new Child(new Super); $obj->render();
うーん、引数でコールバックを渡すくらいしか、思いつかないなぁ。
ひとまず、この方法で使っていきます。