Secrets of the JavaScript Ninja 2nd 読書メモ 第2章 実行時にページを組み立てる

Secrets of the Javascript Ninja

クライアントサイドWebアプリケーションのライフサイクル

  1. ユーザがブラウザのアドレスバーにURLを打ち込む
  2. ブラウザがリクエストを生成してサーバに送信する
  3. サーバがクライアントにレスポンスを返す
  4. ブラウザがHTML/CSS/JavaScriptを処理して最終的なページを組み立てる
  5. ブラウザがイベントキューを監視してイベントに応答できるようにする
  6. ユーザがページの要素を操作する
  7. ユーザがWebアプリケーションを閉じる

ページの組み立てのフェーズ

  1. HTMLを構文解析して、DOMを組み立てる
  2. JavaScriptコードを実行する

※JavaScriptエンジンは、script要素を見つけると、そこでDOMの組み立てを一時停止してJavaScriptを実行する。そのため、script要素がHTMLの途中にあり、読み込みや実行に時間がかかると、ブラウザの描画が停止してパフォーマンスが悪化して感じられる。script要素はbody要素の末尾に置くべきである。

イベントの取り扱い

クライアントサイドのWebアプリケーションはGUIアプリケーションなので、ユーザーの様々な操作(イベント)に反応する必要がある。

イベントハンドリングの概要

ブラウザの実行環境はシングルスレッドの実行モデルである。イベントは単一のキューに入れられ、上から順に1つずつ処理される。

イベントハンドラを登録する

イベントハンドラの登録には以下のいずれかの方法が使用できる。

プロパティへの代入は、一度に一つのイベントハンドラしか登録できない。複数登録したい場合はaddEventListenerを使う必要がある。

Secrets of the JavaScript Ninja 2nd 読書メモ 第1章 JavaScriptはどこにでも

Secrets of the Javascript Ninja

JavaScript言語を理解する

JavaScriptは以下のような点で他の言語と異なる。

  • 関数がファーストクラスオブジェクトである
  • クロージャ
  • スコープ(ES2015より前には、JavaScriptにブロックスコープはなかった)
  • プロトタイプベースのオブジェクト指向

オブジェクトとプロトタイプ、関数とクロージャーの関係を理解することで、JavaScriptのプログラミングスキルが向上する。

加えて、以下のような機能を理解することで、美しく効率の良いコードを書くことができる。

  • ジェネレーター
  • Promiseによる非同期処理
  • Proxyによるオブジェクトへのアクセス制御
  • Arrayのメソッド群
  • Mapによる辞書型コレクション、Setによる一意なコレクション
  • 正規表現
  • モジュール

ブラウザを理解する

JavaScript実行環境としてもっとも重要なのはブラウザである。ここでは以下のトピックに集中する。

  • DOM(Document Object Model)
  • イベント
  • ブラウザのAPI

ベストプラクティスに従う

JavaScriptの習熟に加えて、以下のようなスキルを身につけていることが重要である。

  • デバッグ
  • テスト
  • パフォーマンス分析

スキルを持ち運びやすくする

JavaScriptのコアとなる部分について深い理解があれば、様々な種類のアプリケーション開発にスキルを活用できる。

  • デスクトップアプリ(Electron)
  • モバイルアプリ(Cordova, React Native)
  • サーバーサイドアプリ(Node.js)

Secrets of the JavaScript Ninja 2nd 読書メモ 0 はじめに

Secrets of the Javascript Ninja

『Effective C#』第3版のおかげで、洋書をちまちま読む習慣が身につきました。良い流れを切らさないよう、新しい本に手を付けてみます。次に読む本は『Secrets of the JavaScript Ninja』の第2版。jQueryの作者であるJohn Resig氏の著書で、第1版は「JavaScript Ninjaの極意」というタイトルで邦訳されています。本書は、2016年10月に発売された第2版で、ES6(ES2015)の文法も踏まえた最新版です。

今回は「はじめに(Author’s Introduction)」の部分からメモ。


JavaScriptを取り巻く環境は、著者が本書の初版を書き始めた2008年頃と比べると、様々な点で異なる。

  • JavaScriptはクロスプラットフォームで動作する言語として最大の人気を得ている
  • (JavaScript実行環境の)プラットフォーム間の差異は縮小傾向にある
  • 開発環境の進歩も著しい

JavaScriptの書き方は以前とは様変わりしている。本書には、新しい時代のJavaScriptを、上手に書くための知見がまとめられている。

モダンJavaScriptの練習に:『Electronではじめるアプリ開発』

Electronは、Webの技術(HTML+CSS+JavaScript)を使用してデスクトップアプリケーションを構築するためのフレームワークです。Atom、Visual Studio Codeといったモダンなエディタや、Slackのデスクトップ版などに利用されています。ChromiumというChromeのベースとなっているブラウザを使用することで、Windows/macOS/Linuxといった様々なOS上で動作する、クロスプラットフォームなデスクトップアプリケーションを構築することができます。

本書では、ES2015 + Reactという組み合わせでアプリケーションを構築していきます。ES2015やReactについての詳しい解説は無いので、あらかじめES2015のクラス構文やアロー関数式、Reactの基本的な使い方を身に着けておいたほうが読みやすいでしょう。

ES2015については『改訂新版JavaScript本格入門』がおすすめです。Reactについては、公式チュートリアルをやっておけば大丈夫でしょう。
以下のようなソースを見て、だいたい何をやっているかわかればOKです。

本書には、様々なライブラリを活用して、手早く高機能なアプリケーションを構築する方法が記載されています。また、各章はチュートリアル形式になっているので、実際に手を動かしながら学ぶことができます。Electronに興味がある人だけでなく、モダンなJavaScriptアプリケーションの組み方について理解を深めたい人にもおすすめできる内容です。

『AngularJS: Up and Running』読書メモ 第10章 ngRouteによるルーティング

AngularJS: Up and Running

AngularJSはngRouteというモジュールをオプションで提供している。これを使うと、AngularJSアプリケーションでルーティング(URLに応じた画面遷移)を実装することができる。

※本書が執筆された当時はngRouteが唯一の公式ルーターだったが、2015年にNew Routerというルーターが導入されている。このルーターはAngularJS の1.4以上と2系のいずれでも使えるという特徴がある。

シングルページアプリケーションのルーティング

シングルページアプリケーションにおけるルーティングにおいては、URLとは通常のURLではなく、シバンの追加されたURLである。通常のURLは以下のようなスタイルである。

  • http://example.com/first/page

これに対して、シングルページアプリケーションのURLは以下のようになる。

  • http://example.com/#/first/page
  • http://example.com/#!/first/page

このようになる理由は、ブラウザはハッシュを含むURLを、含まないURLとは違った形で解釈するからである。ブラウザは、#の後ろにある部分をサーバへのリクエストの際に無視する。そのため、#の後ろの部分が変化しても、サーバへのリクエストは行われない。

ngRouteを使う

AngularJSのルーティングモジュールを使う手順は以下のとおり。

1 モジュールのソースコードを読み込む

2 モジュールの依存関係を定義する

3 ページ内のどの部分をAngularJSが変更すべきか印をつける。ngRouteを使う場合、ng-viewディレクティブをHTML内で使う。

4 $routeProviderサービスを使ってconfigセクションでルーティングを定義する

$routeProviderを使うことで、各ルートをwhen()メソッドで定義できる。when()メソッドは、2つの引数をとる。

  • 第1引数はURL、またはこのルートを適用すべきURLの正規表現
  • 第2引数は設定オブジェクトで、そのルートで何が起きるべきかを指定する

otherwize()関数を使うことで、未定義のルートにユーザが遷移しようとした時の振る舞いを決めることができる。

ルーティングオプション

$routeProvider.whenメソッドの第2引数には以下のような形でオプションを渡すことができる。

  • url: URLまたはURLの正規表現。/user/:userId のようにプレースホルダーを設定することも可能
  • template: HTML文字列
  • templateUrl: テンプレートへのパス
  • controller: コントローラー名の文字列又はコントローラーを定義する関数・配列
  • controllerAs: テンプレート内でアクセスするためのコントローラーの別名
  • redirectTo: 別のルートにリダイレクトする
  • resolve: ルートが実行される前に実行される非同期の処理を記述する(認可、認証等に使う)

resolveによるルート実行前のチェック

resolveによって、ルートが読み込まれる前に実行される非同期の処理を定義することができる。

2番目のルート/protectedには、resolveが定義されている。連想配列にimmediateasyncというキーが定義されているが、キーには任意の値を使用できる(myKey1, myKey2でも何でもいい)。連想配列の値は、AngularJSのDIのシンタックスの配列になる。

ルーティングのresolveについて、AngularJSは以下を保証する。

  • resolveが値を返した場合、AngularJSはこの関数の実行を完了し、成功したresolveとして扱う
  • resolveがpromiseオブジェクトを返した場合、AngularJSはpromiseが値を返すのを待って、promiseの結果に応じて成功/失敗として扱う
  • 全てのresolve関数が完了するまで、ルートは読み込まれない
  • 全てのresolveは並列に実行される
  • いずれかのresolveがエラーに遭遇するか、promiseがrejectされたら、AngularJSはそのルートを読み込まない

上のサンプルコードでは、immediateのresolveは、定数を返すだけなので、毎回成功する。asyncはサーバとの通信を行い、成功すれば、ルートが読み込まれる。失敗すると、コントローラーは読み込まれず、対応するHTMLも表示されない。この場合、ユーザーは最後に表示されたページに留まる。

もう一点重要なことは、それぞれのresolveの戻り値は、それぞれのキーによってコントローラーに注入される、ということだ。上のサンプルコードでは、immediateは数値、asyncはpromiseオブジェクトになる。

$routeParamsサービスを使う

Single Page Application(SPA)では、ルートのコンテキストが必要になることが多い。たとえば、/users/1に対して、id=1のユーザの情報を表示する、といった処理が必要になる。

理想的には、コントローラーとルートは、独立してアクセス可能であるべきである。これらのURLパラメータには、$routeParamsサービスを使ってアクセスできる。

このルートにおける、:detIdはプレースホルダーである。ここにある値は$routeParams.detIdで取得できる。

※ngRouteにはURLに制約を付ける機能がないため、/detail/1だけでなく/detail/fooでもアクセスできる。後者の場合、detIdは’foo’になる。

また、URLに含まれるクエリストリングも取得できる。/detail/1?q=fooにアクセスすると、以下のようなデータが取得できる。

注意すべきこと

空のテンプレート

ngRouteは、それぞれのルートが空ではないtemplateまたはtemplateUrlに関連付けられていることを要求する。テンプレートが存在しない場合、ngRouteはこのルートを無視する。空文字列のtemplateも、テンプレートが存在しないとみなされるので注意が必要である。

resolveによるコントローラーへの注入

resolveを使ってコントローラーに値を注入する際は、ng-controllerディレクティブを使用せず、ルーティング定義の一部としてコントローラーを定義すること。そうしないと、AngularJSは依存している値が取得できない。

$routeParamsの値の型

$routeParamsから取得した値は、デフォルトでは全て文字列になる。

1つのアプリケーションにng-viewは1つだけ

ngRouteでは、ng-viewは1つだけ使用可能である。複数のng-viewや、ネストしたng-viewは使用できない。

追加の設定

HTML5モード

ハッシュの含まれるURLはSPAでは一般的なものであるが、ハッシュが含まれない「普通の」URLを使うこともできる。

AngularJSのHTML5モードは、ブラウザのpushState APIを使用する。http://example.com/#/user/1というURLは、HTML5モードではhttp://example.com/user/1となる。

HTML5モードを有効化するには、サーバ側のサポートも必要である。ユーザがhttp://example.com/user/1にアクセスしたとき、サーバのドキュメントルート配下の/user/1ではなく、index.htmlが返るようにする必要がある。

HTML5モードを有効化するには、以下の3つが必要である。

(1) AngularJSのconfigでHTML5モードを有効化する

HTML5モードを有効化するには、$locationProviderを使用する。hashPrefixに’!’を設定することが、SEO上の観点からは推奨される。

(2) <base />タグ

index.html<head>の中に、href属性を含んだ<base />タグを追加する必要がある。これによって、ブラウザに、画像やCSS等の静的リソースをどこから取得すべきかを教える。

たとえば、アプリケーションがhttp://example.com/appというURLで提供されていてる場合、HTML5モードが有効になっていると、URLはhttp://example.com/app/user/1といった形になる。このとき、サーバはindex.htmlを返すが、ブラウザのパスは/app/user/1なので、相対パスではファイルを取得できない。以下のように設定されていれば、この問題を解決できる。

これによって、URLに関わらず、相対パスが/appを起点として解決されるようになる。

(3) サーバサイド

/first/page/second/pageといったルートに対するアクセスの場合にも、index.htmlが返されるようにする必要がある。

以下はNodeJSの場合の実装例。

AngularJSとSEO

検索エンジンについて気に留めておくべき点は以下のとおり。

  • GoogleやBingのような検索エンジンは、SPAに対応したパターンを有している。これらは、hashbangを使用したURLの使用を前提にしている(#ではなく、#!を使用したURL)
  • 検索エンジンがクロールする際、#!?escaped_fragment=に置き換えて、サーバにリクエストを送る

さらに、サーバーが検索エンジンからのリクエストを判別して、以下のような手段を使用してレスポンスを返す必要がある。

  • HTMLのスナップショットを作成して返す: AngularJSのドキュメントはこの方法を使用している
  • HTMLをリアルタイムにレンダリングして返す: PhantomJSのようなヘッドレスブラウザをサーバで使用してHTMLをレンダリングする

angular-seoのようなツールの使用も検討すべきである。

AngularJSと分析サービス

Google Analyticsのような分析サービスは、SPAでは簡単には動かない。分析サービスは、ページの読み込み時のイベントにフックして処理を行っているからである。SPAでは、Google Analyticsを手動で呼び出してルートの変更を教える必要がある。これには、Angularyticsのようなライブラリが使用できる。

代替となるルーター

ngRouteで、70-80%のニーズは満たすことができる。SPAのURLは一般的にシンプルなものになるからである。

しかし、URLに複雑な要求があったり、URLに応じてUIを細かに制御したいような場合には、ngRouteの機能では対応が難しい。

このような場合には、ui-routerの使用を検討すべきである。

※2016年3月現在では、New Routerも代替候補の1つになる。