読者です 読者をやめる 読者になる 読者になる

tanaka's Programming Memo

プログラミングについてのメモ。

パーフェクトJavaScript勉強メモ(21)

前へ | 次へ

21章 サーバサイドJavaScriptとNode.js

サーバサイドJavaScriptの動向

CommonJS

CommonJSとは
  • p473 サーバサイドJavaScriptは、種類ごとに独自のAPIが提供されている
  • JavaScriptの標準APIにはファイル入出力などの主要な機能もないため、独自に用意する機能が多い
  • 標準APIを定めようというのがCommonJSで、2009年初頭に起こった動き
  • 当初はServerJSという名前で開始
  • 標準APIのみならず、コマンドラインツールGUIツールなどまで視野に入れ、改名
  • CommonJSと連動するのがJSGIという規格
  • CommonJSは標準API、JSGIはWebアプリ用のAPI
ホストオブジェクトに依存しないライブラリ
  • jQueryprototype.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関数にモジュール名を引数に呼び出す。戻り値がそのモジュールのexportsオブジェクトに該当する
    • p476 require関数のモジュール名の指定方法は実装依存なので利用するライブラリの仕様に従う
    • モジュールが存在しない場合はError例外オブジェクトを投げる
  • モジュールの応用
    • 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アプリサーバのレイヤまで完結できる
  • PHPRubyなどのスクリプティング系言語と同レイヤ
  • 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の非同期処理の動作
    • Node.jsの非同期書き込み処理は、通常writeメソッドで行い、書き込みメモリバッファに書き込んだらすぐに処理が戻るノンブロッキング関数になっている
    • 効率的に書き込むには、書き込み完了のイベントを待たずにどんどん書き込みを開始すること
    • 容量が大きすぎる場合は、バッファが空いたタイミングのイベントを利用する
    • Node.jsの非同期読み込みはメソッドや関数を使わず、読み込みイベントに対するコールバック関数で行う。イベント名は大抵data
    • 読み込みが完了したらコールバックが呼ばれて、読み込みが完了したデータにアクセスできるようになる
  • モジュール
    • 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プログラミングの概要
  • コールバックを多用
  • イベントループ
    • p488 Node.jsでのポート8080での接続サーバ設定例
    • setTimeoutなどのイベント待ちのコードを書くと、暗黙にイベントループに入る
    • イベントが発火するとコールバックが呼び出され、処理が終わって、イベント待ちが継続される場合は再びイベントループに入る
    • イベント待ちのコードがない場合はスクリプトの最後まで実行するとスクリプトが終了
    • 暗黙の処理が多い。暗黙の処理を明示した例
    • Node.jsではコールバックをイベントに設定するon関数が重要
    • on関数とaddListener関数は別名で同じもの
  • イベント処理の形
    • 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言語のような性質があって取り扱い注意
ストリーム
  • p498 ストリームは「データを読み書きできる機能の抽象」のこと
  • ネット、ファイルなど、データの読み書きの共通性があればストリームと見なせる
  • p499 Node.jsでの読み書きは非同期のもののみが提供
  • Node.jsでの読みにはイベントが必須
  • 書き込みストリーム
    • 読み込みイベント、メソッド一覧
    • p500 writeメソッドで書き込むデータを登録して、完了をイベントで確認
    • 書き込みストリームの主なイベントとメソッド一覧
  • 標準入出力例

前へ | 次へ