JavaScript Decoratorパターン
Decoratorパターンの良いところは、
主体となるオブジェクトと、処理を委譲しているオブジェクトの間に、スッと割り込ませることが出来ること。
そして、その処理を委譲しているオブジェクトには、傷をつけなくて済む。
Decoratorでラッピングするか否か、動的に決めることも出来る。
処理を付け足したり、付け足さなかったり…そんな時に使えるかも。使いたい。けど使いにくい。
function Component() { this.showNumber = function () { console.log(2) } } function Decorator(component) { this.component = component this.showNumber = function () { console.log(1) this.component.showNumber() console.log(3) } } var decorator = new Decorator(new Component()) decorator.showNumber()
1
2
3
Componentの処理のど真ん中に、Decoratorからの処理を埋め込みたい…そんな時もあるかもしれない。
function Component() { this.showNumber = function () { console.log(2) this.decorator.showInnerNumber() console.log(4) } } function Decorator(component) { this.component = component this.component.decorator = this this.showNumber = function () { console.log(1) this.component.showNumber() console.log(5) } this.showInnerNumber = function () { console.log(3) } } var decorator = new Decorator(new Component()) decorator.showNumber()
1
2
3
4
5
DecoratorはComponentを持ち、ComponentもDecoratorを持つ。お互いに委譲し合っている。
イマイチかもしれない。急遽、応急処置が必要な時に使おう。
ループの中のif文が現れたら、Observerパターンを検討する
やはり自分はObserverパターンを理解していなかった。
Observerパターンは、多種多様な輩をループで回す時、真価を発揮する。
以下、よく見るケース。JavaScriptで。
function Student(name, sex) { this.name = name this.sex = sex } var students = [ new Student('野比のび太', 'boy'), new Student('源静香', 'girl'), new Student('剛田武', 'boy'), new Student('骨川スネ夫', 'boy') ] for (var i = 0; i < students.length; i++) { if (students[i].sex === 'boy') { console.log('ムフフ、女子の着替え覗いちゃおうかなぁ。') } else if (students[i].sex === 'girl') { console.log('変態男子!ケダモノ!') } }
ムフフ、女子の着替え覗いちゃおうかなぁ。
変態男子!ケダモノ!
ムフフ、女子の着替え覗いちゃおうかなぁ。
ムフフ、女子の着替え覗いちゃおうかなぁ。
ループの中にif文。分岐が多いのは良くない。
これがネストしたり、else if が何個も続くと、見てるだけで気分が悪くなってくる。
こういう時は、Observerパターンを使う。
function Boy() { Student.apply(this, arguments) } Boy.prototype.say = function () { console.log('ムフフ、女子の着替え覗いちゃおうかなぁ。') } function Girl() { Student.apply(this, arguments) } Girl.prototype.say = function () { console.log('変態男子!ケダモノ!') } var students = [ new Boy('野比のび太', 'boy'), new Girl('源静香', 'girl'), new Boy('剛田武', 'boy'), new Boy('骨川スネ夫', 'boy') ] for (var i = 0; i < students.length; i++) { students[i].say() }
ムフフ、女子の着替え覗いちゃおうかなぁ。
変態男子!ケダモノ!
ムフフ、女子の着替え覗いちゃおうかなぁ。
ムフフ、女子の着替え覗いちゃおうかなぁ。
最後のfor文からifが消えた。
Observerパターンが実はとんでもない汎用性があることに今さら気付いた。
Javascript 関数のデフォルト値設定の罠
今まで何気なくやってた、関数のデフォルト値設定。
function foo(a) { var a = a || 1; ... }
実は愚かな行動だったと知った。
JavaScriptの「&&」「||」について盛大に勘違いをしていた件 - Qiita
例えば、
foo(0); // 1
引数が 0 や false の場合、意図どおりに動作しない。
以下のようにしてやればOK。
// 1 typeof a === 'undefined' && (a = 1) // 2 if (typeof a === 'undefined') a = 1; // 3 a = typeof a !== 'undefined' ? a : 1;
実はJavascript、「短絡評価」をできる、数少ない過激な言語だったと知った。
そこで、すべてのif文は、and or に書き換えられるのでは…、と危険な思想が頭をよぎった。
例えば、
if (a === 1) { console.log(1); } else if (a === 2) { console.log(2); } else { console.log('unknown'); }
個人的には、else if とか大っ嫌い。
以下のように書き換えられるか?
a === 1 && console.log(1) || a === 2 && console.log(2) || console.log('unknown')
うーん、console.log()の関数実行部分が、true を返すか、false を返すかで、挙動が変わってしまう。
いまいちだが、以下のような関数を定義した。
function t() { return true; } function f() { return false; }
&& に続く関数は、絶対に true としたい。
|| に続く関数は、絶対に false としたい。
a === 1 && t(console.log(1)) || a === 2 && t(console.log(2)) || console.log('unknown')
一応、意図した動作になってるっぽい。
独自関数をグローバルに定義してるのはキモいので、何とかするとして、
この Javascript の過激な仕様に感激した。
もともとイカれた(改め、クセの強い)言語だと思っていたが、そこを遥か超えてきやがった。すげぇ。
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のことは考えたくありません。さようなら。