21章 サーバサイドJavaScriptとNode.js
サーバサイドJavaScriptの動向
- p472 ネットスケープ社が提供していた時代があった
- 一度廃れたが復活してきた
- HTML5やAJAXなどや、Java仮想マシン上で動くJVM言語の盛り上がりの影響
- JVMのJavaScript実装であるRhinoにより、Javaの機能を利用したり、不足している機能をJavaで補完できる
- Node.jsの登場。Googleが発表した高速JavaScript実装のv8と非同期ネットワーク処理
- クラウドコンピューティングの中心であるGoogle社のGoogle Apps Scriptにより、Webアプリの拡張言語にサーバサイドJavaScriptを選ばれることが予想される
CommonJS
CommonJSとは
ホストオブジェクトに依存しないライブラリ
- jQueryやprototype.jsはクライアントサイドJavaScript用のライブラリ
- 提供される機能はブラウザのUIであり、グローバルオブジェクトはwindowであるなどの前提がある
- サーバサイドJavaScriptの隆盛に合わせてホストオブジェクトに依存しないライブラリが登場し始めている
- underscore.jsなど
CommonJSの動向
- Node.jsを含む多くのライブラリやフレームワークがCommonJS準拠に向かっている
- ただし、2009年時点では規格の策定などが進んでおらず、Node.jsが非同期ベースなのに対して、CommonJSは同期ベースなので足並みがそろわない
- Node.jsなどが先行して、CommonJSがそれらをフォローしていくことになりそう
モジュール機能
- CommonJSの中で規格化が進んでいるのがモジュール機能
- ある程度の規模のプログラミングをするには、ファイルを分割するなどの実装を分割するモジュール化は必須
- クライアントサイドJavaScriptではHTMLファイル内のscriptタグで複数のJavaScriptファイルを読み込める。しかし、これは単にファイルを連結しているだけであり、グローバル変数の汚染が発生するなどの問題がある
- サーバサイドJavaScriptでも同様で、そのための機能がCommonJSのモジュール規格
- CommonJSのモジュールの単位は実装依存だが、一般的にはファイル単位
- p475 モジュールの具体例
- バージョン1.1.1での説明
- CommonJSのモジュールは、ソースファイル内の名前はグローバルには影響せず、別ファイルの名前にアクセスするにはエクスポートが必要
- ファイルにはexportsとmoduleという2つのオブジェクトが存在
- exportsのプロパティに変数や関数を代入すると、そのプロパティ名で外部からアクセスすることができるようになる
- exportsプロパティへの代入はファイルのどこにあってもよい
- moduleオブジェクトにはidとurlの2つの読み取り専用プロパティがある
- どちらもモジュール名に相当する値を持つ
- モジュール名は、モジュールを識別するための名前
- モジュールがファイル単位であれば、ファイル名そのものと考えてよい
- モジュール名を知るには、idプロパティの値を使う
- モジュールを使うコード
- モジュールの応用
- require関数はmainとpathsの2つのプロパティを持つ
- mainは、moduleオブジェクトと同一判定することで、そのファイルが直接実行されたのか、モジュールとして読み込まれたかが判別できる
- pathsプロパティの値はモジュールを探すファイルパスの配列で、変更することで検索パスを改変できるが、Node.jsなどでは非推奨
Node.js
Node.jsの概要
- p477 サーバサイドJavaScript実装の1つ。正式名はNode
- 特徴は以下のとおり
- JavaScript処理系v8を採用し、ネットワーク周りなどはlibevやlibeioなどの既存Cライブラリを利用
- 非同期処理の汎用イベントループを提供
- 対話的シェル機能もあるコマンドラインツールが付属
- パッケージシステムによる拡張性
- 通常のWebアプリより低レイヤのソフトウェア
- HTTPサーバからWebアプリサーバのレイヤまで完結できる
- PHPやRubyなどのスクリプティング系言語と同レイヤ
- Webアプリを作ることはもちろん、作ろうと思えば何でも作れる
- 書籍の説明は0.4.8ベース
- Node.jsのAPI
- p478 Node.jsでは、プロパティ、メソッド、どのようなイベントを発行するか、イベントに対するコールバック関数の引数の説明がある
- Node.jsのリファレンス http://nodejs.org/docs/latest/api/index.html
- 多くの外部モジュールもある
- 非同期処理とノンブロックイング処理
- Node.jsにおけるブロッキング処理は、内部で待ち状態になりうる関数のことで、ノンブロッキングは待ち状態がない関数
- 同期処理はI/O処理を形容することがメイン
- 同期I/O処理は入力や出力が始まると、それが終わるまで何もしない動作を指す
- 通常は読み込み処理が多い。書き込みはバッファメモリに書き込んで擬似的に完了させてすぐに処理できるようにするので同期にはならない。バッファがいっぱいだった場合は、ブロッキング関数かノンブロッキング関数かによって振る舞いが変わる
- 非同期I/O処理は、読み書き処理をOS側で動かし、読み込みが完了したり、読み込み可能になった段階でイベントが発火する
- 非同期の書き込みは、書き込みが完了したらイベントが発火する。メモリバッファに新しいデータを書き込めるようになったことを意味する
- p479 整理すると、同期I/O処理は、待ち時間が発生する場合はブロッキング関数と同義になる
- 非同期I/O処理は、ブロッキング関数とノンブロッキング関数のいずれも用意することが可能。通常は非同期I/O処理とノンブロッキング関数は一体的
- Node.jsの非同期処理の動作
- モジュール
- p480 CommonJSのモジュール規格に従う
- p481 モジュールの検索順
nodeコマンド
- p482 nodeとコマンドラインに入力すると対話的シェルが起動
- JavaScriptを即時実行できる
- nodeに続けてJavaScriptファイル名を入力すると、そのファイルを実行する
- 先頭行に#!/usr/bin/nodeを書いて実行権限を設定すればそのまま実行可能
npmとパッケージ
- Node Package Manager(npm)は、パッケージシステムのこと
- http://npmjs.org
- パッケージとは、配布するためにモジュールをまとめるためのもの
- p483 インストールするとnpmコマンドが利用可能になり、パッケージの検索、インストール、アンインストールができる
- -gフラグをつけるとグローバルにインストールする
consoleモジュール
- consoleは標準でロードされているモジュールで、すぐに使うことができる
- consoleモジュールの関数一覧
- p484 コンソール利用例
utilモジュール
- 明示的にrequireで読み込んで利用する
- p485 クラスオブジェクトにイベント発行機能を付与するなどが可能
processオブジェクト
- ホストオブジェクトの1つで、単一インスタントのオブジェクト。デフォルトで存在するグローバル変数processで参照できる
- p486 コマンド引数や標準入出力、プロセス終了やその監視、バージョン、動作プラットフォームなどが操作できる
グローバルオブジェクト
- p487 Node.jsではglobalでグローバルオブジェクトにアクセスできる
- Object.keys(global)で列挙できる。あるいは、getOwnPropertyNames
Node.jsプログラミングの概要
- コールバックを多用
- イベントループ
- イベント処理の形
- p489 Node.jsは、イベントにイベントハンドラを追加するイベントドリブンプログラミングスタイルが基本
イベントAPI
- Node.js界では、イベントの発火で呼ばれるコールバック関数をイベントハンドラ、もしくは、イベントリスナと呼ぶ
- イベントは発行元のオブジェクトがある。タイマーイベントのように発行元がはっきりしないものはグローバルとみなせばよい。イベントの発行元オブジェクトをイベントソースと呼ぶ
- 各イベントはイベントソースで一意な文字列(イベント名)で識別される。どのようなイベント名があるかはAPIやNode.jsのソースを読んで確認
- 設定済みのイベントにon関数で新しいイベントハンドラを登録すると、追加登録になる
- 複数のイベントハンドラが呼ばれる順番は通常はonで設定された順で、実務的にそれに依存することはやむを得ない面があるが、本来は避けるべきことなのを認識しておくこと
- 書籍の段階では、複数のイベントチェーンを途中で終える手段はない
- EventEmitterクラス
- p490 Node.jsのイベントソースはeventsモジュールのEventEmitterクラスを継承
- イベント用のメソッド一覧
- listenerメソッドはイベントハンドラの実態(関数の配列)を返す。この配列に直接要素を足したり削除することも可能だが、行儀が悪いので避けること
- イベントハンドラの設定上限をsetMaxListenersメソッドで設定できる。上限を超えるとエラーが発生して、異常なイベント設定を検出できるようになっている。デフォルト値は10
- EventEmitterクラスのインスタンスオブジェクトはnewListenerイベントを発行する。オブジェクトに新しいイベントハンドラが追加されたら発行
- イベントハンドラ内のthis
- イベントハンドラ内で非同期処理を呼ぶ時の注意
- p492 マルチタスクではないので、イベントハンドラを抜けないと、次のイベントは発火しない
- イベントハンドラ内で、他の処理の完了を待つループを作っても、自分自身の処理が終わらないと次の処理は行われないのでうまく動作しない
- p493 イベントハンドラ自身に結果を返させる
- 無名関数を使ってコールバックをネストさせるとコードが読みづらくなるので、コールバック関数を一度定義しておいて代入することも検討の価値あり
- 独自イベント
- Node.jsらしさは独自のイベントソースを作ること
- util.inspect関数で作成できる
- イベント名はerror以外は自由に設定可能
- p494 独自イベントの作成例
バッファ
- JavaScriptの配列は連続しているとは限らず、バイト配列でもない
- p495 その機能を持たせるのがNode.jsのBufferクラス
- new Buffer(1024)のように、確保する容量を渡してバッファを生成
- バッファサイズの変更はできない
- JavaScriptの文字列の内部エンコーディングはUTF-16(UCS2)だが、バイト列にする場合はUTF-8がデフォルト
- Bufferオブジェクトから文字列への変換はtoStringメソッドを使う
- Bufferオブジェクトの要素アクセス
- ブラケットで添え字を指定。0から
- 取り出せるデータはバイトなので0-255
- ブラケットで指定して書き換えも可能
- get,setメソッドでの読み書きも可能
- 範囲外はundefinedが返る
- lengthでバッファのバイト数が返る
- p497 Bufferオブジェクトのメソッド一覧
- Bufferは高速な反面、C言語のような性質があって取り扱い注意