Kanasan.JS JavaScript 第 5 版読書会 #52008年07月25日 11時09分

Kanasan.JS JavaScript 第 5 版読書会 #5 に行ってきました (当日のチャットログ参加者のブログ記事一覧)。今回からはいよいよ第 2 部、クライアントサイドスクリプトということで、13 章全体を読みました。

控えめな JavaScript

JavaScript をどのように使うかの指針として、「控えめな JavaScript (unobtrusive JavaScript)」(WaSP DOM Scripting Task Force の声明文) というキーワードが挙げられています。

  1. HTML マークアップと JavaScript コードを分離する。
  2. 正常に機能を停止する。ブラウザがある機能を持たない、もしくは JavaScript 自体が動作しない場合であっても、コンテンツが利用可能であるようにする。
  3. アクセシビリティを低下させるのではなく、向上させる。

これは非常に重要な指針であり、通常の Web ページでスクリプトを使う際にはぜひ従うべきものだと思います。とはいえ、文書ではなくアプリケーションを提供するときにはスクリプトの使用を必須とせざるを得ない場合もあり、悩むところです。

document.write の挿入位置

document の write メソッドで出力された内容は、多くのブラウザで script 要素の終了直後に挿入されます。仕様ではどうなっているかというと、HTML 4.01 の文書の動的変更に関する箇所 (原文) ではややあいまいながらも script 要素を出力内容で置換するように読み取れます。DOM HTML の write メソッドの定義では読み込み中の文書に対する動作が記されていません。HTML 5 のwrite メソッドの説明ではそのあたりの詳細も定義される予定です。

JavaScript の MIME タイプ

RFC 4329 Scripting Media Types では text/javascript は非推奨となっていますが、IE が application/javascript に対応していないので、当分は text/javascript を使わざるを得ないでしょう (cf. オレオレ言語の MIME タイプ)。

script 要素の defer 属性

スクリプトの実行を後回しにして、先に文書の解析を続行できることを示す属性です。実行を延期されたスクリプトは、実行を延期できないスクリプトに出くわした時点で実行されますとありますが、HTML 4.01 での defer 属性の定義 (原文) にそのようなことは書かれていません。文書の動的変更に関して、すべての SCRIPT 要素は、文書が読み込まれる際の順番で評価されるという文と組み合わせての解釈のようです。HTML 5 の defer 属性の定義では IE の実装に合わせる形で動作が再定義されています。

noscript 要素

「控えめな JavaScript」の考え方に従うなら、noscript 要素を使わなくてもスクリプト無効時の閲覧に支障が出ない文書の作成を目指すべきだと思います。

document.domain

通常、ドメインの異なるページ間ではオブジェクトにアクセスできませんが、アクセスする側される側とも document の domain プロパティに同じ値を設定することで、アクセスできるようになるそうです。ただし、domain プロパティに設定できるのは範囲を広げる方向のみ、つまり、www.example.org のページでは example.org には設定できるけど example.com はだめとのこと。使ったことのないプロパティなので動作もよく知りませんでした。

Same Origin Policy

異なるリソース間でのアクセスで重要となってくる same origin policy、サイ本や竹迫さんのプレゼンでは「同一出身ポリシー」と訳してましたが、私は「同一生成元ポリシー」という訳語を使っています。ちなみにこれを書いている時点でググってみると、「同一出身ポリシー」203 件に対して「同一生成元ポリシー」359 件でした。

プライベート変数の実現方法

KIMOTO さんによる LT。JavaScript で、外部からは見えないが、同じクラス (便宜的にこの語を使います) のインスタンス間では見えるプライベート変数を実現する試みです。コードは次のような感じでした (一部改変)。

var namespace = {};

(function () {
  var seed = 0;
  var p = {};

  namespace.Foo = function (x) {
    var id = "" + seed++;
    this.toString = function () { return id; };

    p[this] = {};
    p[this].x = x;
  };

  namespace.Foo.prototype.getX = function () {
    return p[this].x;
  };

  namespace.Foo.prototype.setX = function (x) {
    p[this].x = x;
  };

  namespace.Foo.prototype.add = function (that) {
    p[this].x += p[that].x;
  };
})();

これなら p は外部から見えないので、アクセサを通さないとメンバ変数にアクセスできないというわけですね。コンストラクタ内でメソッドごとにクロージャを作る方法と違って、メソッドが増えてもインスタンス生成の時間はそれほど変わらないし、ほかのインスタンスのメンバ変数にも直接アクセスできます。この仕組みを考えた後に調べて、これが Perl では inside-out object と呼ばれる手法だと知ったとのことですが、それを自分で考え付いてJavaScriptで実現したというのはすごいです。

Perl の inside-out object との違いとして、JavaScript にはデストラクタがないため、メンバ変数の値がずっと残りっぱなしになってしまうという問題があります。対策としては、自分で後片付け用のメソッドを呼んでやるか、利用者のマシンの性能を信じるかといったところでしょうか。toString メソッドを本来の目的で使えないという声もありましたが、そこは代わりに valueOf メソッドを使って p[+this].x とするのもありかもしれません。

ゆの in ECMAScript 解説

いつまでそのネタを引っ張るんだといわれそうですが、私も LT の時間をもらってゆの in ECMAScript の解説をしてきました。資料は次に挙げますが、これはソースコードそのものの解説というより、ソースを読むに当たっての基礎知識です。

識別子に使える Unicode 文字の「文字」というのは letter であり character ではありません。つまり、記号や空白文字は使えません。正規表現オブジェクトの exec メソッドについては取説 正規表現を参照してください。また、SpiderMonkey では関数を文字列化したときに一部の文字がエスケープされます。

(function () { return "日本語"; }).toString();
// => function () {
// =>     return "\u65E5\u672C\u8A9E";
// => }

以上を踏まえて処理の流れを追っていくと以下のようになります。

  1. ゆの in languageが流行ってるってを評価する。
  2. ゆの.toString() が呼び出される。
  3. グローバル変数 _ を作り、値 "ひだまり" を設定する。
  4. ゆの() を呼び出す。
  5. X.valueOf() が呼び出される。
  6. グローバル変数 _ の値に "ひだまりスケッチ" を設定する。
  7. X / _undefined / "ひだまり" より NaN / NaN となり NaN に評価される。
  8. X.valueOf() が呼び出される。X 自身はもはや valueOf メソッドを持たないので、Object.prototype.valueOfX 上で呼び出され X が返される。
  9. X はプリミティブ値ではないので、X.toString() が呼び出される。
  10. グローバル変数 _ の値に "ひだまりスケッチ×365" を設定する。
  11. NaN / XNaN / undefined より NaN / NaN となり NaN に評価される。
  12. NaN < "来週も見てくださいね"NaN < NaN となり false に評価される。
  13. ゆの.toString() の結果としてゆのを返す。
  14. ゆのはプリミティブ値ではないので、ゆの.valueOf() が呼び出される。
  15. exec メソッドの引数としてゆのを渡したので、ゆの.toString() が呼び出される。ゆの自身はもはや toString メソッドを持たないので、Function.prototype.toStringゆの上で呼び出されゆののソース文字列が返される。
  16. ソース文字列がエスケープされる実装のために、引用符ごと抜き出して eval 関数で目的の文字列に戻す。
  17. グローバル変数そんなことよりヱヴァンゲリヲン新劇場版を作り、値 "ひだまりスケッチ×365 来週も見てくださいね!" を設定する。
  18. ゆの in languageが流行ってるってundefined in { undefined: true } より "undefined" in { "undefined": true } となり true に評価される。
  19. true ? そんなことよりヱヴァンゲリヲン新劇場版 : 破は...そんなことよりヱヴァンゲリヲン新劇場版となり "ひだまりスケッチ×365 来週も見てくださいね!" に評価される。式破は... は評価されない。
  20. プログラム全体の結果として "ひだまりスケッチ×365 来週も見てくださいね!" が返される。

「ゆの in language」という言葉を式中に含めたかったので in 演算子を使ったのですが、in 演算子は真偽値しか返せません。そこで、文字列を返すために条件演算子を使い、in 演算子はその条件式に含めることにしました。破は... (以下省略) の部分は評価されませんが全体としては条件演算子になっており、早くカヲル君が見たいです. ヱヴァンゲリヲン新劇場版はプロパティアクセスとして、序でカヲル君が出てきたときは (以下省略) は関数呼び出しとして解析されます。

条件演算子を使う以上コロンが必要なのですが、タイトルにコロンの入ったアニメ / マンガ / ライトノベルがヱヴァくらいしか思い浮かばなかったのでそれを使いました。後から FF:U があると教えてもらったりJINKI:EXTEND なんてものもあったなと思い出したりしたのですが、両方とも見てないのでそのままです。

雑感

個人的な反省点として、LT の時間を超過しない、発言するときは大きな声でというのが挙げられます。LT に関しては OSC Kansai 2008 閉会式の LT のように全員にわかるタイマーがあるといいのですが、それはスクリーンが二つ以上ないと難しいので、せめて秒読みをつけたほうがいいかもしれません。時間が不定なら減算式ではなく加算式の秒読みでもあるとしまりが出るかなと思います。気軽に話せるという点からすると逆効果になってしまうかもしれませんが。

全体では前日の Shibuya.js in Kyoto からの流れで竹迫さんamachang さん、それに名古屋の JavaScript 勉強会 DeLLa.JS の主催者 issm さんといった豪華メンバーが駆けつけてくださり、読書会、LT、懇親会いずれも大いに盛り上がりました。Kanasan さんはじめスタッフ、参加者の皆さんありがとうございました。ちなみに DeLLa.JS 次回は 8 月 2 日に開催予定とのことなので、名古屋圏の方もそうでない方もぜひ参加してみるといいと思います。